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

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

При работе с иерархическими справочниками в 1С, такими как Подразделения, Номенклатура или СтатьиЗатрат, часто возникает необходимость сгруппировать данные или вывести отчет в разрезе самого верхнего (корневого) уровня. Например, нам нужно знать, к какому головному департаменту относится конкретный сотрудник, независимо от того, насколько глубоко в структуре находится его отдел. Рассмотрим основные сложности этой задачи и разберем все доступные способы её решения.

Проблема отсутствия рекурсии в языке запросов

Прежде всего, проанализируем ситуацию с точки зрения архитектуры платформы. Язык запросов 1С транслируется в SQL, однако он не поддерживает рекурсивные CTE (Common Table Expressions), которые позволяют в «нативном» SQL обходить деревья любой глубины одним оператором. В 1С мы ограничены заранее описанной структурой запроса. Это означает, что если мы хотим получить родителя через поле Родитель, мы должны четко указать количество «шагов» вверх (Родитель.Родитель.Родитель...). Если уровень вложенности в справочнике окажется больше, чем количество прописанных нами обращений, мы не доберемся до корня.

Способ 1: Использование конструкции ВЫБОР КОГДА (Ограниченная вложенность)

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

Пример реализации для иерархии до 4-го уровня:


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

Важный момент: использование такой конструкции создает большое количество «левых соединений» (Left Join) на уровне СУБД. Каждое обращение через точку к полю Родитель — это дополнительное соединение с таблицей справочника. Если вложенность большая (например, 10-15 уровней), производительность запроса может значительно снизиться.

Способ 2: Итоги по иерархии

Посмотрим на встроенный механизм платформы — секцию ИТОГИ. Платформа 1С умеет автоматически рассчитывать итоги с учетом иерархии. Если мы используем конструкцию ИТОГИ ПО ... ТОЛЬКО ИЕРАРХИЯ, система сама построит дерево. Для корневых элементов системная функция Уровень() вернет значение 0.

Рассмотрим пример кода:


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

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

Способ 3: Добавление дополнительного реквизита (Оптимальный путь)

Разберем подход, который считается «стандартом индустрии» в высоконагруженных системах (например, в типовых конфигурациях ERP или УТ 11). Если бизнес-логика требует частого получения корневого элемента, самым правильным решением будет создание в справочнике специального реквизита, например, ГоловноеПодразделение или КорневойУзел.

Алгоритм реализации:

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

Теперь получение корня в запросе становится мгновенным:


ВЫБРАТЬ
    Сотрудники.ФИО,
    Сотрудники.Подразделение.ГоловноеПодразделение КАК ТопУровень
ИЗ
    Справочник.Сотрудники КАК Сотрудники

Преимущества: скорость выполнения запроса O(1), возможность индексации реквизита и отсутствие зависимости от глубины вложенности.

Способ 4: Итерационный подъем через временные таблицы (Псевдорекурсия)

Если изменять структуру конфигурации нельзя, а глубина иерархии неизвестна, применим программно-запросный метод. Мы будем в цикле «подниматься» по дереву родителей, используя временные таблицы. Этот метод позволяет получить корни для любого количества элементов за минимальное число обращений к БД.

Рассмотрим логику процесса:

  1. Создаем временную таблицу ВТ_Данные, куда помещаем исходные элементы. В этой же таблице создаем поле ТекущийРодитель.
  2. В цикле Пока выполняем запрос, который обновляет ТекущийРодитель, подставляя значение ТекущийРодитель.Родитель.
  3. Условием выхода из цикла является отсутствие изменений в таблице или ситуация, когда у всех элементов поле Родитель стало пустым.

Посмотрим на пример упрощенного кода:


МенеджерВТ = Новый МенеджерВременных Таблиц;
// Шаг 1: Помещаем исходные данные в ВТ
Запрос = Новый Запрос("ВЫБРАТЬ Ссылка КАК Элемент, Родитель КАК ТекущийРодитель ПОМЕСТИТЬ ВТ_Корень ...");

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

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

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

Выберем лучший вариант под вашу задачу:

← На главную