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

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

Работа с иерархическими структурами в реляционных базах данных — это классическая задача, которая часто вызывает трудности у разработчиков. В экосистеме 1С нам часто требуется сопоставить каждому родительскому узлу (например, подразделению) абсолютно всех его потомков на всех уровнях вложенности. Рассмотрим ситуацию: у нас есть структура, где Подразделение1 включает в себя Подразделение2, а то, в свою очередь, содержит Подразделение3. Для визуализации подобных связей можно использовать инструменты построения дерева конфигурации. Нам необходимо получить результат, где Подразделение1 связано и с Подразделение2, и с Подразделение3 в одной плоской таблице — для этого подойдёт обработка выгрузки данных 1С с сохранением иерархии.

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

Почему не работает прямой запрос с оператором В ИЕРАРХИИ?

Разберем типичную ошибку начинающих разработчиков. Попытка написать запрос вида ПО Таблица2.Ссылка В ИЕРАРХИИ (Таблица1.Ссылка) приведет к ошибке конструктора запроса или времени выполнения. Проанализируем причину: в языке запросов 1С оператор В ИЕРАРХИИ ожидает в качестве аргумента либо конкретную ссылку, либо массив ссылок, либо список значений. Он не может принимать на вход имя колонки другой таблицы в рамках соединения.

На уровне СУБД (SQL) платформа 1С преобразует В ИЕРАРХИИ в сложную конструкцию, использующую технические поля (индексы иерархии), чтобы быстро найти подчиненные элементы. Однако механизм генерации SQL-кода в 1С не поддерживает динамическое вычисление иерархии для каждой строки соединяемой таблицы. Поэтому нам приходится искать обходные пути, используя профессиональные консоли запросов и отчетов для отладки сложных выборок — для этого подойдёт инструментарий разработчика с консолью запросов и СКД.

Решение 1: Запрос с фиксированным количеством уровней вложенности

Если в вашей конфигурации глубина справочника Подразделения ограничена (например, не более 5-6 уровней), можно применить метод последовательных левых соединений или использовать средства интерактивного формирования запросов для получения родителей. Рассмотрим пример запроса, который позволяет собрать иерархию, обращаясь к родителю каждого уровня.

Проанализируем структуру такого запроса:


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

Важное замечание: Этот метод прост в реализации, но имеет существенный недостаток — он не является универсальным. Если структура разрастется до 7 уровней, элементы седьмого уровня не попадут в выборку для самого верхнего родителя. Кроме того, большое количество обращений через точку (разыменований) в условии ПО может негативно сказаться на производительности при большом объеме данных.

Решение 2: Рекурсивный обход иерархии в коде

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

Рассмотрим пример реализации на встроенном языке:


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

Выясним причину, по которой этот метод не рекомендуется для больших справочников. Здесь возникает проблема "N+1 запроса": если в вашей иерархии 1000 подразделений, система выполнит 1000 отдельных запросов к базе данных. Это создаст колоссальную нагрузку на сервер и сеть.

Решение 3: Оптимальный обход дерева в памяти

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

Разберем этот алгоритм по шагам:

  1. Выполним один запрос, чтобы получить все подразделения (Ссылка и Родитель) в таблицу значений.
  2. Создадим рекурсивную функцию, которая будет искать строки в этой таблице по колонке Родитель.
  3. Вместо запроса в цикле будем использовать метод ТаблицаПодразделений.НайтиСтроки(Новый Структура("Родитель", ТекущийРодитель)).

Это на порядки быстрее, так как обращение к базе данных происходит всего один раз.

Решение 4: Использование Системы компоновки данных (СКД)

Если задача стоит в рамках построения отчета, то СКД — самый мощный инструмент для работы с иерархией. В СКД есть механизм Связи наборов данных, который позволяет реализовать рекурсию на уровне платформы.

Проанализируем настройку такой связи:

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

Решение 5: Метод "Таблица замыканий" (Closure Table)

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

Суть метода заключается в создании дополнительного регистра сведений ИерархияПодразделений с измерениями Родитель и Потомок. В этот регистр записываются все возможные связи:

При таком подходе получение всей иерархии для Подразделения 1 превращается в простейший запрос к этому регистру:


ВЫБРАТЬ Потомок ИЗ РегистрСведений.ИерархияПодразделений ГДЕ Родитель = &НужноеПодразделение

Преимущество: Мгновенное получение результата одним запросом без использования В ИЕРАРХИИ и без рекурсии. Недостаток: Необходимо программно поддерживать актуальность этого регистра при изменении родителей в справочнике подразделений.

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

Выбор метода зависит от ваших целей. Рассмотрим краткие рекомендации:

  1. Для простых отчетов с небольшой вложенностью используйте запрос с левыми соединениями или разыменованием полей.
  2. Для сложных отчетов с неограниченной вложенностью всегда выбирайте СКД и связи наборов данных.
  3. Для обработки данных в коде используйте однократную выгрузку в Таблицу Значений с последующим поиском в памяти.
  4. Для критически важных по скорости участков кода в крупных системах внедряйте вспомогательный регистр связей (Closure Table).

Помните, что рекурсивные запросы в цикле (как в Сообщении 18) — это крайняя мера, допустимая только на очень маленьких объемах данных или в разовых обработках.

← На главную