Как в 1С вывести всю глубину рекурсии по реквизиту справочника

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разберем распространенную задачу: в неиерархическом справочнике есть реквизит, который ссылается на элемент этого же справочника (образуя цепочку ссылок произвольной глубины). Нам требуется получить всю цепочку вложенности для отчета или обработки, не зная заранее точную глубину дерева.

В языке запросов 1С отсутствуют рекурсивные конструкции наподобие WITH CTE в классическом SQL. Рассмотрим подробнее основные архитектурные подходы для решения этой задачи в платформе 1С:Предприятие.

Вариант 1. Настройка произвольной иерархии в СКД (рекомендуемый для отчетов)

Если задача заключается в формировании отчета, самым элегантным и производительным решением без изменения структуры конфигурации является механизм иерархических связей в Системе компоновки данных (СКД). Разберем настройку по шагам:

  1. Создадим один набор данных — запрос, выбирающий необходимые поля: Ссылка, реквизит-родитель (например, База) и прочие нужные реквизиты.
  2. Перейдем на вкладку Связи наборов данных и добавим связь набора данных самого с собой (в полях источника и приемника выбирается один и тот же набор данных).
  3. В поле Выражение источника укажем Ссылка, а в поле Выражение приемника — База.
  4. Установим флаг Иерархическая связь.
  5. В поле Начальные значения укажем условие для элементов верхнего уровня (например, ЗНАЧЕНИЕ(Справочник.ИмяСправочника.ПустаяСсылка) или NULL, если родитель не заполнен).
  6. В настройках структуры отчета в выбранной группировке укажем тип иерархии Иерархия вместо «Без иерархии».

При таком подходе платформа 1С автоматически раскрутит дерево связей на любую глубину без написания программного кода.

Вариант 2. Программная постобработка данных на встроенном языке

Если результат рекурсии нужен в кодовой обработке или для сложной модификации данных, лучшим решением будет выгрузка данных в память и построение цепочки средствами платформы в один запрос. Выполнять запросы в цикле крайне не рекомендуется, так как это создает избыточную нагрузку на СУБД.

Посмотрим на пример построения дерева с контролем циклических ссылок:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Спр.Ссылка КАК Ссылка,
    |	Спр.База КАК База,
    |	Спр.Наименование КАК Наименование
    |ИЗ
    |	Справочник.Номенклатура КАК Спр";

ТаблицаДанных = Запрос.Выполнить().Выгрузить();
ТаблицаДанных.Индексы.Добавить("Ссылка");
ТаблицаДанных.Индексы.Добавить("База");

// Создаем соответствие для быстрого поиска цепочек связей
СоответствиеРодителей = Новый Соответствие;
Для Каждого Стр Из ТаблицаДанных Цикл
	СоответствиеРодителей.Вставить(Стр.Ссылка, Стр.База);
КонецЦикла;

Теперь разберем, как получить цепочку родителей для конкретного элемента с защитой от зацикливания:


Функция ПолучитьЦепочкуРодителей(ТекСсылка, СоответствиеРодителей)
	Цепочка = Новый Массив;
	Посещенные = Новый Соответствие;
	
	Текущий = ТекСсылка;
	Пока ЗначениеЗаполнено(Текущий) Цикл
		Если Посещенные[Текущий] <> Неопределено Тогда
			// Обнаружена циклическая ссылка, прерываем обход
			Прервать;
		КонецЕсли;
		
		Посещенные.Вставить(Текущий, Истина);
		Родитель = СоответствиеРодителей[Текущий];
		
		Если ЗначениеЗаполнено(Родитель) Тогда
			Цепочка.Добавить(Родитель);
		КонецЕсли;
		
		Текущий = Родитель;
	КонецЦикла;
	
	Возврат Цепочка;
КонецФункции

Вариант 3. Использование транзитивного замыкания (в регистре сведений)

Если дерево связей содержит миллионы записей, а запросы к полной иерархии выполняются непрерывно в высоконагруженных процессах, оптимальным решением является паттерн транзитивного замыкания (Transitive Closure).

  1. Создадим непериодический регистр сведений, например, ИерархияЭлементов, с измерениями Элемент (Ссылка), Предок (Ссылка) и ресурсом УровеньГлубины (Число).
  2. При записи элемента справочника в модуле объекта программно перезаписываем все пары предок-потомок для текущего узла и его подчиненных элементов.
  3. В целевых запросах выборка всех предков или всех потомков выполняется простым соединением с регистром сведений по одной строке условия без применения рекурсивных обходов.

Ограничения и риски методов на уровне SQL-запросов

Проанализируем подходы, которых следует избегать:

  1. Динамическая генерация левых соединений: построение запроса с 10–20 соединениями ЛЕВОЕ СОЕДИНЕНИЕ одной таблицы к самой себе приводит к резкому усложнению плана выполнения запроса в СУБД (MS SQL, PostgreSQL), чрезмерному потреблению оперативной памяти и падению производительности. Кроме того, этот метод ограничивает максимальную глубину константой в коде.
  2. Рекурсивный цикл запросов с временными таблицами: цикл, создающий и обновляющий временные таблицы в базе данных на каждом шаге итерации, создает избыточную нагрузку на системную базу данных tempdb и провоцирует блокировки.
← На главную