При работе с многоуровневыми иерархическими справочниками в 1С часто возникает задача: для произвольного элемента определить его самого верхнего родителя, то есть корневой элемент его ветки. Это может понадобиться для фильтрации, построения отчетов или для реализации специфической бизнес-логики. Давайте разберем несколько эффективных способов решения этой задачи как для старой платформы 1С 7.7, так и для современных версий 1С 8.х.
Самый простой и надежный способ — это последовательный подъем вверх по иерархии от текущего элемента к его родителям до тех пор, пока мы не достигнем самого верхнего уровня. Логика здесь элементарна: мы берем текущий элемент, проверяем, есть ли у него родитель. Если есть, то этот родитель становится нашим новым текущим элементом, и мы повторяем проверку. Цикл завершается, когда мы доходим до элемента, у которого родителя уже нет.
Этот подход универсален и хорошо работает как на старых, так и на новых платформах, отличаясь лишь синтаксисом.
Пример для 1С 7.7
В "семерке" для проверки пустого значения используется функция ПустоеЗначение(). Код может выглядеть так:
// Предположим, что в переменной "Элемент" у нас ссылка на исходный элемент справочника
Папа = Элемент.Родитель;
// Запускаем цикл, который будет работать, пока у "Папы" есть свой родитель
Пока ПустоеЗначение(Папа.Родитель) = 0 Цикл
// Перемещаемся на один уровень вверх
Папа = Папа.Родитель;
КонецЦикла;
// После завершения цикла в переменной "Папа" будет ссылка на родителя первого уровня
// Если у исходного элемента не было родителей, "Папа" будет содержать ссылку на него самого
Сообщить(Папа);
Пример для 1С 8.х
На платформе 8.х синтаксис становится более современным. Вместо ПустоеЗначение() используется метод ссылки .Пустая(). Часто эту логику оформляют в виде функции для переиспользования.
Функция ПолучитьРодителяПервогоУровня(СправочникСсылка)
// Делаем копию ссылки, чтобы не изменять исходный параметр
ТекущийЭлемент = СправочникСсылка;
// Цикл работает, пока у текущего элемента родитель не является пустой ссылкой
Пока НЕ ТекущийЭлемент.Родитель.Пустая() Цикл
// Переходим к родителю
ТекущийЭлемент = ТекущийЭлемент.Родитель;
КонецЦикла;
// Возвращаем найденный корневой элемент
Возврат ТекущийЭлемент;
КонецФункции
// Пример вызова:
// ИсходныйЭлемент = Справочники.Номенклатура.НайтиПоКоду("00123");
// КорневойРодитель = ПолучитьРодителяПервогоУровня(ИсходныйЭлемент);
// Сообщить(КорневойРодитель);
Альтернативой циклу является рекурсия. Мы создаем функцию, которая вызывает саму себя для родителя переданного элемента. Этот метод выглядит более "элегантным" с точки зрения кода, но его следует использовать с осторожностью, так как очень глубокая иерархия (сотни уровней) может привести к ошибке переполнения стека вызовов.
Пример рекурсивной функции (для 1С 7.7)
Рассмотрим пример функции ДайПапу. Она проверяет, выбран ли родитель у текущего элемента. Если да, то она вызывает саму себя для этого родителя. Если нет — возвращает текущий элемент, который и является вершиной иерархии.
Функция ДайПапу(Элемент)
// Проверяем, есть ли у элемента родитель и он не пустой
Если Элемент.Родитель.Выбран() = 1 Тогда
// Если родитель есть, вызываем эту же функцию для него
Возврат ДайПапу(Элемент.Родитель);
КонецЕсли;
// Если родителя нет, значит, мы на вершине. Возвращаем текущий элемент.
Возврат Элемент;
КонецФункции
// Пример использования:
// МойЭлемент = СоздатьОбъект("Справочник.Номенклатура");
// МойЭлемент.НайтиПоКоду("...");
// Корень = ДайПапу(МойЭлемент);
Для платформы 1С 8.х самым производительным и идиоматичным способом является использование языка запросов. Специальная конструкция ИТОГИ ПО ... ТОЛЬКО ИЕРАРХИЯ позволяет получить всю иерархическую цепочку от корня до указанного элемента. Первый элемент в результирующей выборке и будет искомым родителем первого уровня.
Этот метод особенно эффективен при работе с большим объемом данных, так как все вычисления производятся на стороне сервера СУБД.
Пример запроса
Функция ПолучитьРодителяПервогоУровняЗапросом(ЭлементСсылка)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Элемент
|
|ИТОГИ ПО
| Ссылка ТОЛЬКО ИЕРАРХИЯ";
Запрос.УстановитьПараметр("Элемент", ЭлементСсылка);
РезультатЗапроса = Запрос.Выполнить();
// Обходим результат по группировкам, так как используется ИЕРАРХИЯ
Выборка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
// Первый элемент в такой выборке - всегда корень
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
Иначе
Возврат Неопределено;
КонецЕсли;
КонецФункции
Важное замечание: Приведенный запрос найдет корень для одного элемента. Если нужно выполнить эту операцию для множества элементов, запрос следует модифицировать для пакетной обработки, чтобы избежать выполнения запросов в цикле.
Платформа 1С 7.7 предоставляет удобные встроенные методы для работы с иерархией, которые могут избавить от необходимости писать циклы или рекурсию.
ПолныйКод(): Возвращает строку, содержащую коды всех родительских элементов и код самого элемента, разделенные символом-разделителем (обычно `/` или `\`). Код родителя первого уровня будет первым в этой строке.ПолноеНаименование(): Работает аналогично, но возвращает строку с наименованиями.Пример использования ПолныйКод() в 1С 7.7
// Получаем ссылку на элемент
Элемент = СоздатьОбъект("Справочник.Номенклатура");
Элемент.НайтиПоНаименованию("Саморез 4.8*152");
// Получаем полный код
ПолныйКодЭлемента = Элемент.ПолныйКод(); // Например, "Группа1/ПодгруппаА/00123"
// Находим позицию первого разделителя
ПозицияРазделителя = Найти(ПолныйКодЭлемента, "/");
Если ПозицияРазделителя > 0 Тогда
// Если разделитель найден, извлекаем код корневого родителя
КодКорня = Лев(ПолныйКодЭлемента, ПозицияРазделителя - 1);
// Находим элемент по этому коду
Корень = СоздатьОбъект("Справочник.Номенклатура");
Если Корень.НайтиПоКоду(КодКорня) = 1 Тогда
Сообщить("Родитель первого уровня: " + Корень.Наименование);
КонецЕсли;
Иначе
Сообщить("Элемент сам является родителем первого уровня.");
КонецЕсли;
В высоконагруженных системах, где поиск корневого элемента выполняется очень часто, можно рассмотреть варианты с денормализацией данных для ускорения доступа.
РодительПервогоУровня. При записи или изменении элемента специальным кодом (в обработчике ПередЗаписью) определять его корень и сохранять ссылку в этот реквизит. Таким образом, для получения корня не потребуется обход иерархии, а достаточно будет прочитать значение одного поля. Минус — усложнение логики записи и необходимость пересчета при изменении структуры иерархии.Элемент, Родитель и ресурсом Уровень. При изменении справочника фоновое задание может перестраивать данные в этом регистре. Это позволяет строить очень быстрые запросы к плоской таблице регистра для получения любой информации об иерархии.Эти подходы являются более сложными в реализации, но могут быть оправданы при высоких требованиях к производительности.