Родитель элемента справочника в 1С 8.3: свойство, иерархия, запросы и подводные камни

· 9 мин

Содержание

Родитель элемента справочника в 1С 8.3 хранится в стандартном реквизите Родитель. Проверка непосредственного родителя — это одно сравнение: Ссылка.Родитель = Группа. Если нужно узнать, лежит ли элемент в группе на любой глубине, используйте метод Ссылка.ПринадлежитЭлементу(Группа) или условие В ИЕРАРХИИ в запросе.

Всё это работает только у иерархического справочника и только на сервере (или в толстом клиенте). На тонком клиенте и в веб-клиенте реквизит через точку от ссылки не прочитать — его нужно получить серверным вызовом или взять из данных формы.

Что такое родитель и когда он вообще есть

Реквизит Родитель появляется у справочника, если в конфигураторе на вкладке «Иерархия» включён флажок «Иерархический справочник». У неиерархического справочника такого реквизита нет. Обращение Ссылка.Родитель к нему закончится ошибкой Поле объекта не обнаружено (Родитель).

Иерархия бывает двух видов (свойство ТипИерархии):

Вид иерархииКто может быть родителемПример
Иерархия групп и элементовТолько группа (ЭтоГруппа = Истина)Номенклатура, Контрагенты
Иерархия элементовЛюбой элементПодразделения, Статьи затрат в некоторых конфигурациях

Для второго вида реквизита ЭтоГруппа нет, и проверять его бессмысленно.

Не путайте Родитель и Владелец. Владелец — это ссылка на элемент другого справочника, которому подчинён текущий (например, договор подчинён контрагенту). Родитель — это группа или элемент в пределах того же справочника.

Тип иерархии и сам факт иерархичности можно проверить кодом, если процедура универсальная и получает справочник извне:

МетаСправочник = Метаданные.Справочники.Номенклатура;
Если МетаСправочник.Иерархический Тогда
    Если МетаСправочник.ТипИерархии = Метаданные.СвойстваОбъектов.ТипИерархииСправочника.ИерархияГруппИЭлементов Тогда
        // родителем может быть только группа
    КонецЕсли;
КонецЕсли;

Для произвольной ссылки метаданные берут через Ссылка.Метаданные().

Свойство Родитель: прямая проверка

Самый частый сценарий: «этот товар лежит в группе „Услуги“?». Если интересует только непосредственная группа, хватит сравнения ссылок.

&НаСервере
Функция ЭлементВГруппе(Элемент, Группа)
    Возврат Элемент.Родитель = Группа;
КонецФункции

Ссылки в 1С сравниваются по значению. Две ссылки на один и тот же объект всегда равны, даже если получены разными путями. Сравнивать наименования (Элемент.Родитель.Наименование = "Услуги") не нужно: имена групп меняют, а ещё бывают одноимённые группы на разных уровнях.

Ссылку на группу для сравнения берут так:

  • 📌 предопределённый элемент — Справочники.Номенклатура.Услуги;
  • 📌 по коду — Справочники.Номенклатура.НайтиПоКоду("00-00000012");
  • 📌 параметр, который выбрал пользователь на форме;
  • 📌 константа или настройка, где хранится ссылка.

НайтиПоНаименованию возвращает первый найденный элемент. При двух группах с одинаковым именем вы получите случайную. Для проверки родителя лучше код или предопределённый элемент.

Элемент верхнего уровня

Если элемент лежит в корне, его Родитель — пустая ссылка этого справочника. Проверить это можно тремя равноценными способами:

Если Элемент.Родитель.Пустая() Тогда ...
Если Не ЗначениеЗаполнено(Элемент.Родитель) Тогда ...
Если Элемент.Родитель = Справочники.Номенклатура.ПустаяСсылка() Тогда ...

ЗначениеЗаполнено удобнее в универсальном коде: он не требует знать имя справочника.

Проверка из формы элемента

В форме элемента справочника реквизит Родитель уже есть в основном реквизите формы Объект. Серверный вызов не нужен:

&НаКлиенте
Процедура ПроверитьГруппу(Команда)
    Если Не ЗначениеЗаполнено(Объект.Родитель) Тогда
        Сообщить("Элемент в корне справочника");
    КонецЕсли;
КонецПроцедуры

А вот родителя родителя на клиенте уже не получить: Объект.Родитель.Родитель в тонком клиенте не работает . Для этого нужен серверный вызов, о нём ниже.

Проверка вложенности на любую глубину

Сравнение Родитель = Группа вернёт Ложь, если товар лежит в подгруппе: «Услуги → Монтаж → Кондиционеры». Для проверки по всей ветке у ссылки справочника есть метод ПринадлежитЭлементу:

&НаСервере
Функция ВходитВВетку(Элемент, Группа)
    Возврат Элемент.ПринадлежитЭлементу(Группа);
КонецФункции

Метод поднимается по цепочке родителей и возвращает Истина, если встретит указанную группу на любом уровне выше элемента.

Ещё два полезных метода ссылки иерархического справочника:

  • 🔹 Уровень() — номер уровня элемента, верхний уровень — 0;
  • 🔹 ПолноеНаименование() — путь вида Услуги/Монтаж/Кондиционеры, удобно для сообщений и отладки.

Своя функция обхода вверх

Иногда нужно не «да/нет», а вся цепочка родителей: вывести путь, найти ближайшую группу с заполненной ставкой или счётом учёта. Тогда пишут обход вверх с защитой от бесконечного цикла:

&НаСервере
Функция ЦепочкаРодителей(Элемент)
    Цепочка = Новый Массив;
    Текущий = Элемент.Родитель;
    Шаг = 0;
    Пока ЗначениеЗаполнено(Текущий) И Шаг < 100 Цикл
        Цепочка.Добавить(Текущий);
        Текущий = Текущий.Родитель;
        Шаг = Шаг + 1;
    КонецЦикла;
    Возврат Цепочка; // от ближайшей группы к корневой
КонецФункции

Ограничение в 100 шагов — страховка. В нормальной базе такой глубины не бывает, но после кривой загрузки данных или прямой правки таблиц цикл в иерархии возможен.

Как не создать цикл при переносе

Перед программным переносом группы в другую группу проверьте, что новый родитель не лежит внутри переносимой ветки. Иначе получится петля «А внутри Б, Б внутри А».

Если НовыйРодитель = Группа Или НовыйРодитель.ПринадлежитЭлементу(Группа) Тогда
    ВызватьИсключение "Нельзя перенести группу внутрь самой себя";
КонецЕсли;

Деревья на форме: ПолучитьРодителя()

Название GetParent из англоязычных материалов часто путают со свойством справочника. У ссылки справочника такого метода нет: в английском синтаксисе это просто свойство Ref.Parent.

Метод ПолучитьРодителя() (GetParent()) существует у строки дерева в данных формы — ДанныеФормыЭлементДерева. Он нужен, когда справочник выведен на управляемую форму реквизитом типа «Дерево значений»:

&НаКлиенте
Процедура ДеревоПриАктивизацииСтроки(Элемент)
    ТекущаяСтрока = Элементы.Дерево.ТекущиеДанные;
    Если ТекущаяСтрока = Неопределено Тогда
        Возврат;
    КонецЕсли;
    СтрокаРодитель = ТекущаяСтрока.ПолучитьРодителя();
    Если СтрокаРодитель = Неопределено Тогда
        // строка верхнего уровня дерева
    КонецЕсли;
КонецПроцедуры

Здесь всё работает на клиенте и без обращения к базе: родитель — это строка дерева, а не ссылка из справочника.

У обычного ДеревоЗначений на сервере вместо метода используется свойство строки СтрокаДерева.Родитель. Для строки верхнего уровня оно равно Неопределено, а не пустой ссылке.

Где работаетеКак получить родителяЗначение у верхнего уровня
Ссылка справочника (сервер)Ссылка.РодительПустая ссылка
Данные формы ОбъектОбъект.РодительПустая ссылка
Дерево в данных формыСтрока.ПолучитьРодителя()Неопределено
ДеревоЗначений на сервереСтрока.РодительНеопределено

Запросы: В ИЕРАРХИИ и разбор структуры

Если элементов много, проверять родителя по одному в цикле — плохая идея. Запрос отвечает сразу за всю выборку.

Все элементы внутри группы

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Номенклатура.Ссылка КАК Ссылка
|ИЗ
|    Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|    Номенклатура.Ссылка В ИЕРАРХИИ (&Группа)
|    И НЕ Номенклатура.ЭтоГруппа";
Запрос.УстановитьПараметр("Группа", Справочники.Номенклатура.Услуги);
Выборка = Запрос.Выполнить().Выбрать();

В ИЕРАРХИИ включает в результат и саму группу-параметр. Поэтому в примере стоит отбор НЕ ЭтоГруппа: он убирает и её, и подгруппы.

Только непосредственные потомки — условие Номенклатура.Родитель = &Группа.

Проверить пачку элементов разом

Задача «какие из 500 товаров документа относятся к услугам» решается одним запросом, а не 500 вызовами ПринадлежитЭлементу:

Запрос.Текст =
"ВЫБРАТЬ
|    Номенклатура.Ссылка КАК Ссылка
|ИЗ
|    Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|    Номенклатура.Ссылка В (&СписокТоваров)
|    И Номенклатура.Ссылка В ИЕРАРХИИ (&Группа)";
Запрос.УстановитьПараметр("СписокТоваров", МассивТоваров);
Запрос.УстановитьПараметр("Группа", ГруппаУслуг);

То же условие В ИЕРАРХИИ работает и для поля табличной части или регистра, если у поля тип ссылки на иерархический справочник: ГДЕ Продажи.Номенклатура В ИЕРАРХИИ (&Группа).

Родители нескольких уровней в одной выборке

Поля через точку в запросе превращаются в соединения. Для вывода двух-трёх уровней это нормально:

"ВЫБРАТЬ
|    Н.Ссылка,
|    Н.Родитель КАК Группа1,
|    Н.Родитель.Родитель КАК Группа2
|ИЗ
|    Справочник.Номенклатура КАК Н"

Если групп больше трёх-четырёх уровней, лучше построить итоги: ИТОГИ ПО Ссылка ИЕРАРХИЯ и обойти результат через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией).

Прямой SQL к таблицам

В файловой базе прямого доступа нет, а в клиент-серверной родитель лежит в поле _ParentIDRRef таблицы справочника. Имя таблицы вида _Reference45 у каждой базы своё, его показывает ПолучитьСтруктуруХраненияБазыДанных().

Прямой SQL годится только для чтения и диагностики. Запись в таблицы в обход платформы ломает кэш и ссылочную целостность. Для поиска петель в иерархии этого достаточно, для исправления — нет: правьте через 1С.

Пустые, битые и неподходящие значения

Большинство ошибок при проверке родителя — не в логике, а во входных данных.

Диагностика: выберите симптом

Надёжная обёртка, которая выдерживает любые входные данные:

&НаСервереБезКонтекста
Функция РодительЭлемента(Значение)
    Если Не ЗначениеЗаполнено(Значение) Тогда
        Возврат Неопределено;
    КонецЕсли;
    Если Не Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)) Тогда
        Возврат Неопределено;
    КонецЕсли;
    Если Не Значение.Метаданные().Иерархический Тогда
        Возврат Неопределено;
    КонецЕсли;
    Возврат Значение.Родитель;
КонецФункции

Функция возвращает Неопределено там, где родителя быть не может, и пустую ссылку там, где элемент лежит в корне. Вызывающий код различает эти случаи.

Битые ссылки на родителя

Битая ссылка заполнена (ЗначениеЗаполнено вернёт Истина), но объекта за ней нет. Проверка:

Если ЗначениеЗаполнено(Элемент.Родитель)
    И Элемент.Родитель.ПолучитьОбъект() = Неопределено Тогда
    // группа удалена, ссылка битая
КонецЕсли;

ПолучитьОбъект() читает объект целиком, поэтому для массовой проверки используйте запрос: левое соединение справочника с самим собой по Родитель и отбор, где правая сторона ЕСТЬ NULL при заполненном родителе.

Скорость на больших справочниках

На справочнике в 50–100 тысяч элементов разница между подходами видна сразу.

  • 🐢 Цикл по выборке с Элемент.Родитель.Родитель — каждое обращение через точку читает объект из базы. На 10 000 строк и трёх уровнях это десятки тысяч чтений.
  • 🐢 ПринадлежитЭлементу в цикле — удобно для одного элемента, медленно для тысячи.
  • ⚡ Запрос с В ИЕРАРХИИ — одно обращение к базе на всю выборку.
  • ⚡ Чтение только нужного реквизита — в типовых конфигурациях на БСП есть ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Родитель"). Оно делает запрос к одному полю, а не загружает весь объект.

Ещё приёмы, которые дают ощутимый выигрыш:

  1. Если в одном вызове нужно много раз проверить вхождение в одну и ту же группу, один раз получите запросом все её элементы в Соответствие и дальше проверяйте Соответствие.Получить(Элемент) <> Неопределено.
  2. Не передавайте на сервер элемент за элементом. Соберите массив ссылок на клиенте и сделайте один серверный вызов.
  3. В динамическом списке используйте отбор по Родитель или В иерархии в настройках списка, а не перебор строк.
  4. Ограничьте глубину иерархии в свойствах справочника, если по смыслу она не нужна. Флажок «Ограничение количества уровней» не даст пользователям строить ветки в десять уровней.

Замер делайте не на глаз. Включите в конфигураторе замер производительности (Отладка → Замер производительности) и сравните варианты на реальной копии базы, а не на демо с сотней элементов.

Частые вопросы

Можно ли проверить родителя у элемента, который ещё не записан?
Да. У нового объекта реквизит Родитель заполнен в самом объекте (Объект.Родитель), ссылки ещё нет, но сравнивать родителя можно. Метод ПринадлежитЭлементу у объекта не вызвать — используйте его у ссылки родителя или обход вверх.
Почему запрос с В ИЕРАРХИИ возвращает саму группу?
Так работает условие: группа считается частью собственной иерархии. Отсеките её отбором НЕ ЭтоГруппа или условием Ссылка <> &Группа.
Как программно переместить элемент в другую группу?
Получите объект через ПолучитьОбъект(), присвойте Родитель новую группу и вызовите Записать(). Перед этим проверьте, что новая группа не лежит внутри переносимой ветки.
Чем отличается проверка Родитель от Владелец?
Родитель — группа того же справочника, Владелец — элемент другого справочника, которому подчинён текущий. Для подчинённых справочников проверяют Владелец, иерархия при этом может и отсутствовать.
Как найти все группы верхнего уровня?
Запросом с условием Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) и ЭтоГруппа. Тот же отбор можно задать в настройках динамического списка.