Родитель элемента справочника в 1С 8.3: свойство, иерархия, запросы и подводные камни
Содержание
Родитель элемента справочника в 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 строк и трёх уровнях это десятки тысяч чтений. - 🐢
ПринадлежитЭлементув цикле — удобно для одного элемента, медленно для тысячи. - ⚡ Запрос с
В ИЕРАРХИИ— одно обращение к базе на всю выборку. - ⚡ Чтение только нужного реквизита — в типовых конфигурациях на БСП есть
ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Родитель"). Оно делает запрос к одному полю, а не загружает весь объект.
Ещё приёмы, которые дают ощутимый выигрыш:
- Если в одном вызове нужно много раз проверить вхождение в одну и ту же группу, один раз получите запросом все её элементы в
Соответствиеи дальше проверяйтеСоответствие.Получить(Элемент) <> Неопределено. - Не передавайте на сервер элемент за элементом. Соберите массив ссылок на клиенте и сделайте один серверный вызов.
- В динамическом списке используйте отбор по
РодительилиВ иерархиив настройках списка, а не перебор строк. - Ограничьте глубину иерархии в свойствах справочника, если по смыслу она не нужна. Флажок «Ограничение количества уровней» не даст пользователям строить ветки в десять уровней.
Замер делайте не на глаз. Включите в конфигураторе замер производительности (Отладка → Замер производительности) и сравните варианты на реальной копии базы, а не на демо с сотней элементов.