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