При работе с иерархическими справочниками в 1С, такими как Подразделения, Номенклатура или СтатьиЗатрат, часто возникает необходимость сгруппировать данные или вывести отчет в разрезе самого верхнего (корневого) уровня. Например, нам нужно знать, к какому головному департаменту относится конкретный сотрудник, независимо от того, насколько глубоко в структуре находится его отдел. Рассмотрим основные сложности этой задачи и разберем все доступные способы её решения.
Прежде всего, проанализируем ситуацию с точки зрения архитектуры платформы. Язык запросов 1С транслируется в SQL, однако он не поддерживает рекурсивные CTE (Common Table Expressions), которые позволяют в «нативном» SQL обходить деревья любой глубины одним оператором. В 1С мы ограничены заранее описанной структурой запроса. Это означает, что если мы хотим получить родителя через поле Родитель, мы должны четко указать количество «шагов» вверх (Родитель.Родитель.Родитель...). Если уровень вложенности в справочнике окажется больше, чем количество прописанных нами обращений, мы не доберемся до корня.
Рассмотрим самый очевидный, но наименее универсальный способ. Мы можем вручную перебрать уровни иерархии, проверяя наличие родителя у элемента. Выясним причину популярности этого метода: он работает внутри любого сложного запроса без использования внешнего кода. Однако его надежность напрямую зависит от того, насколько глубокая иерархия допустима в вашем справочнике.
Пример реализации для иерархии до 4-го уровня:
ВЫБРАТЬ
Продажи.Номенклатура КАК Товар,
ВЫБОР
КОГДА Продажи.Номенклатура.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ТОГДА Продажи.Номенклатура
КОГДА Продажи.Номенклатура.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ТОГДА Продажи.Номенклатура.Родитель
КОГДА Продажи.Номенклатура.Родитель.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ТОГДА Продажи.Номенклатура.Родитель.Родитель
ИНАЧЕ Продажи.Номенклатура.Родитель.Родитель.Родитель
КОНЕЦ КАК КорневаяГруппа
ИЗ
РегистрНакопления.Продажи КАК Продажи
Важный момент: использование такой конструкции создает большое количество «левых соединений» (Left Join) на уровне СУБД. Каждое обращение через точку к полю Родитель — это дополнительное соединение с таблицей справочника. Если вложенность большая (например, 10-15 уровней), производительность запроса может значительно снизиться.
Посмотрим на встроенный механизм платформы — секцию ИТОГИ. Платформа 1С умеет автоматически рассчитывать итоги с учетом иерархии. Если мы используем конструкцию ИТОГИ ПО ... ТОЛЬКО ИЕРАРХИЯ, система сама построит дерево. Для корневых элементов системная функция Уровень() вернет значение 0.
Рассмотрим пример кода:
ВЫБРАТЬ
Подразделения.Ссылка КАК Подразделение,
Подразделения.Наименование
ИЗ
Справочник.Подразделения КАК Подразделения
ГДЕ
Подразделения.Ссылка = &НужноеПодразделение
ИТОГИ ПО
Подразделение ТОЛЬКО ИЕРАРХИЯ
Проанализируем ситуацию: этот метод идеален, если вам нужно просто вывести результат в отчет или программно перебрать дерево в ВыборкаИзРезультатаЗапроса. Однако у него есть критический недостаток: результат итогов невозможно использовать как источник для временной таблицы или вложить в другой запрос. Если корень нужен вам для дальнейших вычислений внутри одного запроса, этот метод не подойдет.
Разберем подход, который считается «стандартом индустрии» в высоконагруженных системах (например, в типовых конфигурациях ERP или УТ 11). Если бизнес-логика требует частого получения корневого элемента, самым правильным решением будет создание в справочнике специального реквизита, например, ГоловноеПодразделение или КорневойУзел.
Алгоритм реализации:
ПередЗаписью прописываем логику: если у текущего элемента нет родителя, записываем в реквизит саму ссылку (или пусто), если родитель есть — копируем значение этого реквизита у родителя.Теперь получение корня в запросе становится мгновенным:
ВЫБРАТЬ
Сотрудники.ФИО,
Сотрудники.Подразделение.ГоловноеПодразделение КАК ТопУровень
ИЗ
Справочник.Сотрудники КАК Сотрудники
Преимущества: скорость выполнения запроса O(1), возможность индексации реквизита и отсутствие зависимости от глубины вложенности.
Если изменять структуру конфигурации нельзя, а глубина иерархии неизвестна, применим программно-запросный метод. Мы будем в цикле «подниматься» по дереву родителей, используя временные таблицы. Этот метод позволяет получить корни для любого количества элементов за минимальное число обращений к БД.
Рассмотрим логику процесса:
ВТ_Данные, куда помещаем исходные элементы. В этой же таблице создаем поле ТекущийРодитель.Пока выполняем запрос, который обновляет ТекущийРодитель, подставляя значение ТекущийРодитель.Родитель.Родитель стало пустым.Посмотрим на пример упрощенного кода:
МенеджерВТ = Новый МенеджерВременных Таблиц;
// Шаг 1: Помещаем исходные данные в ВТ
Запрос = Новый Запрос("ВЫБРАТЬ Ссылка КАК Элемент, Родитель КАК ТекущийРодитель ПОМЕСТИТЬ ВТ_Корень ...");
// Шаг 2: Цикл обновления
Пока Истина Цикл
Запрос.Текст =
"ВЫБРАТЬ
| ВТ.Элемент,
| ВЫБОР КОГДА ВТ.ТекущийРодитель.Родитель = ЗНАЧЕНИЕ(Справочник.Подразделения.ПустаяСсылка)
| ТОГДА ВТ.ТекущийРодитель
| ИНАЧЕ ВТ.ТекущийРодитель.Родитель
| КОНЕЦ КАК НовыйРодитель
|ИЗ ВТ_Корень КАК ВТ";
// ... логика обновления ВТ и проверка на завершение
// Если НовыйРодитель везде равен ТекущийРодитель - выходим
КонецЦикла;
Мы проанализировали ситуацию и выяснили, что «красивого» универсального способа получить корень только одним текстом запроса без ограничений вложенности в 1С не существует.
Выберем лучший вариант под вашу задачу:
ВЫБОР КОГДА.ИТОГИ ПО ИЕРАРХИИ.