В практике разработки на платформе 1С:Предприятие часто возникает стандартная задача: отобрать элементы, входящие в определенную группу. Для этого мы используем привычный оператор В ИЕРАРХИИ. Однако иногда задача ставится «наоборот»: у нас есть список конечных элементов (номенклатуры), и нам нужно найти все группы (родителей любого уровня), к которым эти элементы относятся. Это необходимо, например, для фильтрации регистров, где хранятся настройки по группам номенклатуры, или для построения сложных отчетов.
В рамках данной статьи мы разберем несколько эффективных способов решения этой задачи: от классических подходов до использования современных возможностей платформы, таких как рекурсивные запросы.
Многие разработчики привыкли, что оператор В ИЕРАРХИИ принимает в качестве параметра только конкретную ссылку или массив ссылок. Но платформа позволяет использовать в качестве аргумента и поле таблицы. Рассмотрим ситуацию, когда у нас есть временная таблица ВТ_Группы (те группы, которые мы проверяем) и временная таблица ВТ_СписокНоменклатуры (наши товары).
Проанализируем следующий пример запроса:
ВЫБРАТЬ
ВТ_Группы.Ссылка КАК Группа
ИЗ
ВТ_Группы КАК ВТ_Группы
ГДЕ
ЕСТЬ NULL (
ВЫБРАТЬ ПЕРВЫЕ 1
1
ИЗ
ВТ_СписокНоменклатуры КАК ВТ_Товары
ГДЕ
ВТ_Товары.Ссылка В ИЕРАРХИИ (ВТ_Группы.Ссылка)
) ЕСТЬ НЕ NULL
В данном случае для каждой строки из ВТ_Группы система будет выполнять проверку: есть ли в списке товаров хотя бы один, который входит в иерархию этой группы. Важно помнить, что такой запрос может работать медленно на больших объемах данных, так как СУБД приходится выполнять сложные вложенные операции для сопоставления иерархических связей. Тем не менее, для небольших списков это самый простой и читаемый код.
С выходом версии платформы 8.3.20 у разработчиков появился мощный инструмент — Common Table Expressions (CTE), позволяющий писать рекурсивные запросы. Это «золотой стандарт» для работы с деревьями. Мы можем «развернуть» иерархию каждого товара из списка вверх до корня и получить плоский список всех его родителей.
Разберем по шагам, как это работает:
Родитель.Пример структуры такого запроса:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Родитель КАК Родитель
ПОМЕСТИТЬ ВТ_ВсеПредки
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка В (&СписокНоменклатуры)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ВТ_ВсеПредки.Ссылка,
СправочникНоменклатура.Родитель
ИЗ
ВТ_ВсеПредки КАК ВТ_ВсеПредки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
ПО ВТ_ВсеПредки.Родитель = СправочникНоменклатура.Ссылка
ГДЕ
СправочникНоменклатура.Ссылка <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ВТ_ВсеПредки.Родитель КАК ИскомаяГруппа
ИЗ
ВТ_ВсеПредки КАК ВТ_ВсеПредки
ГДЕ
ВТ_ВсеПредки.Родитель <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
Этот метод является самым производительным, так как он задействует оптимизированные механизмы СУБД для работы с рекурсией и не требует перебора всех записей справочника «вниз».
Если ваша конфигурация работает на старой версии платформы или вы проектируете высоконагруженную систему, можно использовать метод, который часто упоминается экспертами (например, ildarovich). Суть заключается в создании технического Регистра Сведений, в котором хранятся пары: «Элемент — Его предок» для всех уровней вложенности.
Например, для структуры «Группа 1 -> Группа 2 -> Товар 1» в регистре будут записи:
В этом случае проверка «является ли группа родителем любого уровня» превращается в простейшее и максимально быстрое соединение двух таблиц по равенству ссылок:
ВЫБРАТЬ
РегистрИерархии.Родитель
ИЗ
РегистрСведений.ИерархияНоменклатуры КАК РегистрИерархии
ГДЕ
РегистрИерархии.Объект В (&СписокНоменклатуры)
Преимущество: колоссальная скорость чтения. Недостаток: необходимость поддерживать актуальность этого регистра при записи элементов в справочник.
Если вы точно знаете, что вложенность вашего справочника ограничена (например, не более 3-4 уровней), можно прибегнуть к простому соединению через несколько обращений к родителям. Рассмотрим ситуацию, когда нам нужно проверить принадлежность к группе &ГруппаНоменклатуры.
Проанализируем условие в запросе:
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
(Номенклатура.Родитель = &ГруппаНоменклатуры
ИЛИ Номенклатура.Родитель.Родитель = &ГруппаНоменклатуры
ИЛИ Номенклатура.Родитель.Родитель.Родитель = &ГруппаНоменклатуры)
Несмотря на кажущуюся громоздкость, такой запрос часто работает быстрее, чем В ИЕРАРХИИ, так как SQL-серверу гораздо проще обрабатывать прямые равенства (JOIN), чем раскрывать рекурсивную функцию платформы. Однако этот метод не универсален и подходит только для фиксированных структур.
Если задача стоит именно так, как описано в сообщении 8 — «выбрать только группы из регистра, которые являются родителями для списка товаров», — мы можем использовать соединение таблицы групп из регистра с таблицей товаров по иерархическому условию и сгруппировать результат.
ВЫБРАТЬ
Т_Регистр.ГруппаНоменклатуры,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т_Товары.Ссылка) КАК КолВоТоваров
ИЗ
РегистрСведений.НастройкиПоГруппам КАК Т_Регистр
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_СписокТоваров КАК Т_Товары
ПО Т_Товары.Ссылка В ИЕРАРХИИ (Т_Регистр.ГруппаНоменклатуры)
СГРУППИРОВАТЬ ПО
Т_Регистр.ГруппаНоменклатуры
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т_Товары.Ссылка) > 0
Выясним причину выбора этого метода: он идеален, когда нужно не просто проверить одного родителя, а отфильтровать целый набор «потенциальных» родителей, хранящихся в базе данных. Платформа 1С эффективно транслирует В ИЕРАРХИИ во временные таблицы SQL (или конструкцию EXISTS), что позволяет получить корректный результат.
Мы рассмотрели различные подходы к определению иерархических связей «снизу вверх». Выбор конкретного метода зависит от версии вашей платформы и требований к производительности:
Важно помнить, что работа с иерархией в запросах всегда сопряжена с нагрузкой на СУБД. Рекомендуется индексировать поля Родитель и всегда использовать временные таблицы для предварительной фильтрации данных перед выполнением сложных иерархических соединений.