При работе с иерархическими справочниками в 1С:Предприятие, такими как Номенклатура, Контрагенты или Подразделения, часто возникает необходимость определить «самого старшего» предка для конкретного элемента. Например, если у нас есть структура Товары (0 уровень) — Оргтехника (1 уровень) — Принтеры (2 уровень) — Принтер HP (элемент), нам может потребоваться программно получить ссылку на группу Товары.
Сложность заключается в том, что глубина вложенности может быть произвольной, и простое обращение к свойству Родитель не всегда решает задачу. В этой статье мы подробно рассмотрим все возможные подходы: от простых циклов до продвинутых архитектурных решений.
Это самый распространенный и интуитивно понятный метод. Мы последовательно поднимаемся вверх по дереву иерархии, пока не встретим пустую ссылку в поле Родитель. Проанализируем, как это реализовать наиболее эффективно.
Рассмотрим базовый алгоритм обхода. Для того чтобы не нагружать сервер лишними вызовами, рекомендуется работать со ссылками или использовать метод Уровень().
// Функция получает ссылку на элемент и возвращает его корневого родителя
Функция ПолучитьКорневогоРодителя(ЭлементСсылка) Экспорт
Если ЭлементСсылка.Пустая() Тогда
Возврат Справочники.Номенклатура.ПустаяСсылка();
КонецЕсли;
ТекущийОбъект = ЭлементСсылка;
// Цикл продолжается, пока у текущего элемента есть родитель
Пока ЗначениеЗаполнено(ТекущийОбъект.Родитель) Цикл
ТекущийОбъект = ТекущийОбъект.Родитель;
КонецЦикла;
Возврат ТекущийОбъект;
КонецФункции
Оптимизация: В платформе 1С 8.3 у ссылок справочников есть метод Уровень(). Мы можем заранее узнать количество итераций цикла, что сделает код более структурированным:
КоличествоУровней = ЭлементСсылка.Уровень();
ТекущийРодитель = ЭлементСсылка;
Для Сч = 1 По КоличествоУровней Цикл
ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;
Корень = ТекущийРодитель;
Важно помнить, что обращение через точку (ТекущийРодитель.Родитель) в цикле может порождать множество мелких запросов к базе данных, если объект не кэширован. Если элементов много, лучше использовать запросы.
Язык запросов 1С не поддерживает рекурсию напрямую (в отличие от стандартного SQL с CTE). Однако, если глубина иерархии в вашей базе ограничена (например, не более 5-10 уровней), мы можем применить метод последовательных левых соединений.
Разберем пример запроса, который находит корень для элемента при вложенности до 3-х уровней:
ВЫБРАТЬ
ЕСТЬNULL(РодительУровень2.Ссылка,
ЕСТЬNULL(РодительУровень1.Ссылка,
ЕСТЬNULL(Номенклатура.Родитель, Номенклатура.Ссылка))) КАК КорневойРодитель
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК РодительУровень1
ПО Номенклатура.Родитель = РодительУровень1.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК РодительУровень2
ПО РодительУровень1.Родитель = РодительУровень2.Ссылка
ГДЕ
Номенклатура.Ссылка = &Ссылка
Этот метод очень быстр, но он не универсален. Если пользователь создаст группу 11-го уровня, запрос вернет не корень, а родителя 10-го уровня.
Если задача стоит в рамках построения отчета, то СКД — лучший инструмент. В СКД реализован механизм связей наборов данных, который умеет обрабатывать иерархию автоматически.
Проанализируем шаги настройки:
Родитель с полем Ссылка.При использовании группировки "Иерархия" в настройках отчета, 1С сама построит дерево, и корень всегда будет на верхнем уровне группировки.
Для высоконагруженных систем, где поиск корня требуется постоянно (например, в правилах ценообразования или в больших динамических списках), постоянное выполнение циклов или сложных запросов недопустимо. Профессиональный подход заключается в денормализации.
Рассмотрим шаги реализации:
Номенклатура новый реквизит КорневойРодитель (тип: СправочникСсылка.Номенклатура).ПередЗаписью прописываем логику заполнения:
Процедура ПередЗаписью(Отказ)
Если ЭтоНовый() Или Родитель <> СсылкаНаОбъектВБазе.Родитель Тогда
Если НЕ ЗначениеЗаполнено(Родитель) Тогда
// Если родителя нет, элемент сам является корнем (или корень пустой)
КорневойРодитель = Ссылка;
Иначе
// Получаем корень родителя (рекурсивно или циклом)
// В идеале: КорневойРодитель = Родитель.КорневойРодитель;
КорневойРодитель = ОбщегоНазначения.ПолучитьКорневойУзел(Родитель);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Преимущество: Теперь в любом запросе или списке мы можем мгновенно получить корень просто обратившись к полю Номенклатура.КорневойРодитель. Это радикально повышает производительность.
Это классический метод из теории баз данных. Мы создаем вспомогательный регистр сведений СвязиИерархии с измерениями Элемент и Предок. Для каждого элемента справочника в этот регистр записываются все его вышестоящие родители.
Например, для элемента "Принтер HP" записи будут такими:
Чтобы найти корень, нам достаточно выполнить простой запрос к этому регистру:
ВЫБРАТЬ ПЕРВЫЕ 1
СвязиИерархии.Предок
ИЗ
РегистрСведений.СвязиИерархии КАК СвязиИерархии
ГДЕ
СвязиИерархии.Элемент = &НужныйЭлемент
УПОРЯДОЧИТЬ ПО
СвязиИерархии.Уровень УБЫВ
Иногда требуется быстро узнать имя корневой группы без обращения к метаданным. Рассмотрим метод ПолныйКод() или ПолноеНаименование(). Эти методы возвращают строку вида "Товары / Оргтехника / Принтеры / Принтер HP".
Мы можем разбить эту строку, используя разделитель "/". Первая часть строки и будет наименованием корневого элемента.
ПолныйПуть = ОбъектСсылка.ПолноеНаименование();
ПозицияРазделителя = Найти(ПолныйПуть, "/");
Если ПозицияРазделителя > 0 Тогда
ИмяКорня = СокрЛП(Лев(ПолныйПуть, ПозицияРазделителя - 1));
КонецЕсли;
Внимание: Этот метод ненадежен для логики программы, так как он возвращает строку, а не ссылку. К тому же, если в названии самой группы содержится слэш, логика сломается. Используйте его только для оперативного вывода информации в отчетах "на скорую руку".
Мы проанализировали различные ситуации и выяснили следующее:
Пока.ОбщегоНазначения, там часто уже есть готовые функции для работы с иерархией.Выбор метода зависит от ваших требований к производительности и чистоте архитектуры. Для большинства типовых задач программного обхода в цикле на сервере вполне достаточно.