Как в запросе 1С получить все подчиненные элементы справочника любой вложенности?

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

В практике разработчика 1С часто возникает задача получения полной структуры подчиненности. Например, когда необходимо соотнести департамент со всеми его нижестоящими подразделениями (отделами, группами, службами) вне зависимости от того, на каком уровне иерархии они находятся. Стандартный подход с использованием конструкции Родитель.Родитель...Родитель крайне неудобен, так как он жестко ограничивает глубину вложенности и делает код «хрупким» при изменении структуры компании.

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

Проблема использования жесткой иерархии в запросах

Рассмотрим типичный запрос, который часто пишут новички. В нем делается попытка «дотянуться» до родителя через несколько уровней вложенности. Проанализируем пример из обсуждения:


ВЫБРАТЬ
    Отделы.Ссылка КАК Отдел,
    Депатрамент.Ссылка КАК Департамент
ИЗ
    Справочник.ПодразделенияКомпании КАК Отделы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ПодразделенияКомпании КАК Депатрамент
    ПО (Отделы.Ссылка = Депатрамент.Ссылка 
    ИЛИ Отделы.Родитель = Депатрамент.Ссылка 
    ИЛИ Отделы.Родитель.Родитель = Депатрамент.Ссылка 
    ИЛИ Отделы.Родитель.Родитель.Родитель = Депатрамент.Ссылка)
ГДЕ
    Депатрамент.ВидПодразделения = &Департамент

Главный недостаток такого подхода: если структура компании расширится до пятого или шестого уровня, запрос перестанет выдавать корректные данные. Кроме того, такие запросы крайне тяжело читать и оптимизировать. В языке SQL, на котором базируется платформа 1С, для таких задач обычно используются рекурсивные обобщенные табличные выражения (CTE), но, к сожалению, в стандартном языке запросов 1С поддержка CTE отсутствует.

Решение 1: Использование оператора В ИЕРАРХИИ

Самый простой способ отобрать все дочерние элементы — использовать конструктор В ИЕРАРХИИ. Этот оператор встроен в платформу и оптимизирован для работы с иерархическими данными. Если нам нужно просто получить список всех «детей» для конкретного департамента, мы можем написать следующий запрос:


ВЫБРАТЬ
    Подразделения.Ссылка КАК Подразделение
ИЗ
    Справочник.ПодразделенияКомпании КАК Подразделения
ГДЕ
    Подразделения.Ссылка В ИЕРАРХИИ(&ВыбранныйДепартамент)

Однако этот метод решает задачу только наполовину. Он позволяет отфильтровать данные, но не позволяет получить готовую таблицу соответствий «Департамент — Подразделение», если департаментов несколько и нам нужно сопоставить каждого ребенка со своим «корневым» предком.

Решение 2: Система компоновки данных (СКД)

Если ваша задача — построить отчет, то Система компоновки данных является идеальным инструментом. В СКД реализован механизм «Связи наборов данных», который поддерживает иерархические связи. Рассмотрим, как это работает:

  1. Создаем основной набор данных (запрос), который выбирает все подразделения.
  2. В закладке «Связи наборов данных» указываем связь набора с самим собой.
  3. В поле «Источник связи» указываем Ссылка, в поле «Приемник» — Родитель.
  4. Устанавливаем флажок Иерархическая связь.

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

Решение 3: Программный рекурсивный обход

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


Процедура ПолучитьДочерниеЭлементы(ТекущийРодитель, ТаблицаРезультат)
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	Подразделения.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.ПодразделенияКомпании КАК Подразделения
    |ГДЕ
    |	Подразделения.Родитель = &Родитель";
    
    Запрос.УстановитьПараметр("Родитель", ТекущийРодитель);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = ТаблицаРезультат.Добавить();
        НоваяСтрока.Подразделение = Выборка.Ссылка;
        // Рекурсивный вызов для поиска детей текущего элемента
        ПолучитьДочерниеЭлементы(Выборка.Ссылка, ТаблицаРезультат);
    КонецЦикла;
КонецПроцедуры

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

Решение 4: Метод транзитивного замыкания (Итерационный запрос)

Проанализируем более продвинутый метод, который позволяет получить всю иерархию за ограниченное количество обращений к базе данных. Смысл заключается в том, чтобы в цикле наполнять временную таблицу связями «Предок — Потомок».

Алгоритм действий следующий:

  1. Выбираем во временную таблицу все прямые связи (Родитель — Ссылка).
  2. В цикле выполняем запрос, который соединяет текущую временную таблицу со справочником: ВТ.Потомок = Справочник.Родитель.
  3. Добавляем найденные новые связи в итоговую таблицу.
  4. Завершаем цикл, когда новых записей больше не найдено.

Такой подход позволяет за 5-10 итераций обработать структуру любой сложности, при этом общее количество запросов будет равно глубине иерархии, а не количеству элементов.

Решение 5: Обход результата запроса с иерархией

Если нам нужно программно обработать дерево, мы можем воспользоваться возможностью платформы выгружать иерархические итоги. Посмотрим на пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Подразделения.Ссылка КАК Ссылка,
|	Подразделения.Наименование КАК Наименование
|ИЗ
|	Справочник.ПодразделенияКомпании КАК Подразделения
|УПОРЯДОЧИТЬ ПО
|	Ссылка ИЕРАРХИЯ";

РезультатЗапроса = Запрос.Выполнить();
// Используем специальный тип обхода
Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);

Метод Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) превращает плоский результат запроса в объект ДеревоЗначений. После этого мы можем легко обойти его, зная, что все дочерние элементы находятся в коллекции Строки своего родителя.

Подведение итогов

Выясним, какой метод лучше выбрать в зависимости от ситуации:

  1. Для простых отчетов: Используйте СКД и иерархические связи наборов данных. Это самый быстрый и надежный путь.
  2. Для фильтрации в динамических списках: Используйте оператор В ИЕРАРХИИ.
  3. Для сложных алгоритмов в коде: Оптимальным будет итерационный подход с временными таблицами или выгрузка в ДеревоЗначений с последующим обходом.

Помните, что универсального «вечного» запроса для плоской таблицы связей в 1С без использования вспомогательных инструментов (циклов или СКД) не существует из-за ограничений языка запросов. Однако, используя предложенные методы, вы сможете создавать решения, которые не зависят от изменений структуры справочников и работают стабильно годами.

← На главную