Разберем распространенную задачу: в неиерархическом справочнике есть реквизит, который ссылается на элемент этого же справочника (образуя цепочку ссылок произвольной глубины). Нам требуется получить всю цепочку вложенности для отчета или обработки, не зная заранее точную глубину дерева.
В языке запросов 1С отсутствуют рекурсивные конструкции наподобие WITH CTE в классическом SQL. Рассмотрим подробнее основные архитектурные подходы для решения этой задачи в платформе 1С:Предприятие.
Если задача заключается в формировании отчета, самым элегантным и производительным решением без изменения структуры конфигурации является механизм иерархических связей в Системе компоновки данных (СКД). Разберем настройку по шагам:
Ссылка, реквизит-родитель (например, База) и прочие нужные реквизиты.Ссылка, а в поле Выражение приемника — База.ЗНАЧЕНИЕ(Справочник.ИмяСправочника.ПустаяСсылка) или NULL, если родитель не заполнен).При таком подходе платформа 1С автоматически раскрутит дерево связей на любую глубину без написания программного кода.
Если результат рекурсии нужен в кодовой обработке или для сложной модификации данных, лучшим решением будет выгрузка данных в память и построение цепочки средствами платформы в один запрос. Выполнять запросы в цикле крайне не рекомендуется, так как это создает избыточную нагрузку на СУБД.
Посмотрим на пример построения дерева с контролем циклических ссылок:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка,
| Спр.База КАК База,
| Спр.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Спр";
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
ТаблицаДанных.Индексы.Добавить("Ссылка");
ТаблицаДанных.Индексы.Добавить("База");
// Создаем соответствие для быстрого поиска цепочек связей
СоответствиеРодителей = Новый Соответствие;
Для Каждого Стр Из ТаблицаДанных Цикл
СоответствиеРодителей.Вставить(Стр.Ссылка, Стр.База);
КонецЦикла;
Теперь разберем, как получить цепочку родителей для конкретного элемента с защитой от зацикливания:
Функция ПолучитьЦепочкуРодителей(ТекСсылка, СоответствиеРодителей)
Цепочка = Новый Массив;
Посещенные = Новый Соответствие;
Текущий = ТекСсылка;
Пока ЗначениеЗаполнено(Текущий) Цикл
Если Посещенные[Текущий] <> Неопределено Тогда
// Обнаружена циклическая ссылка, прерываем обход
Прервать;
КонецЕсли;
Посещенные.Вставить(Текущий, Истина);
Родитель = СоответствиеРодителей[Текущий];
Если ЗначениеЗаполнено(Родитель) Тогда
Цепочка.Добавить(Родитель);
КонецЕсли;
Текущий = Родитель;
КонецЦикла;
Возврат Цепочка;
КонецФункции
Если дерево связей содержит миллионы записей, а запросы к полной иерархии выполняются непрерывно в высоконагруженных процессах, оптимальным решением является паттерн транзитивного замыкания (Transitive Closure).
ИерархияЭлементов, с измерениями Элемент (Ссылка), Предок (Ссылка) и ресурсом УровеньГлубины (Число).Проанализируем подходы, которых следует избегать:
ЛЕВОЕ СОЕДИНЕНИЕ одной таблицы к самой себе приводит к резкому усложнению плана выполнения запроса в СУБД (MS SQL, PostgreSQL), чрезмерному потреблению оперативной памяти и падению производительности. Кроме того, этот метод ограничивает максимальную глубину константой в коде.tempdb и провоцирует блокировки.