Как запросом получить все группы справочника, содержащие особенные элементы в иерархии

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

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

Понимание механики иерархии в 1С

Прежде чем переходить к решениям, проанализируем ситуацию. Как было отмечено в обсуждении, каждый элемент справочника в базе данных «знает» только своего непосредственного родителя. Когда мы используем конструкцию ГДЕ Ссылка В ИЕРАРХИИ (&Группа), 1С генерирует SQL-запрос с таким количеством соединений «самого с собой», сколько уровней вложенности определено в метаданных справочника или сколько реально существует в базе. Если уровней много, такой запрос становится тяжелым для сервера.

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

Решение 1: Связи наборов данных в СКД (Метод для отчетов)

Если задача решается в рамках системы компоновки данных (СКД), мы можем воспользоваться механизмом связей наборов данных. Этот метод наиболее прост в реализации, но имеет нюансы производительности.

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

  1. Создадим основной набор данных (например, НаборДанныхГруппы), который выбирает список групп из вашей таблицы значений.
  2. Создадим второй набор данных типа «Запрос» (НаборДанныхПроверка) со следующим текстом:

ВЫБРАТЬ ПЕРВЫЕ 1
    &ПараметрГруппа КАК Группа,
    ИСТИНА КАК ЕстьОсобенныйЭлемент
ИЗ
    Справочник.МойСправочник КАК МойСправочник
ГДЕ
    МойСправочник.Ссылка В ИЕРАРХИИ(&ПараметрГруппа)
    И МойСправочник.Особенный = ИСТИНА
    И МойСправочник.ЭтоГруппа = ЛОЖЬ

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

Важный нюанс: Этот метод заставляет систему выполнять отдельный SQL-запрос для каждой строки из первого набора данных. Если групп в таблице много, это может существенно замедлить формирование отчета. Однако для небольших объемов данных это самое быстрое и элегантное решение.

Решение 2: Метод транзитивного замыкания (Логарифмическое удвоение)

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

Суть метода заключается в том, чтобы создать таблицу всех связей «Предок — Потомок». Сначала мы берем прямых родителей, затем соединяем таблицу саму с собой, чтобы получить дедушек, затем — прадедушек и так далее. Благодаря удвоению дистанции (1, 2, 4, 8, 16...), мы можем покрыть справочник глубиной в 32 уровня всего за 5 соединений.

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


// 1. Получаем прямые связи Ссылка-Родитель
ВЫБРАТЬ
    Ссылка КАК Потомок,
    Родитель КАК Предок
ПОМЕСТИТЬ ВТ_Иерархия
ИЗ
    Справочник.МойСправочник
ГДЕ
    Родитель <> ЗНАЧЕНИЕ(Справочник.МойСправочник.ПустаяСсылка)

ИНДЕКСИРОВАТЬ ПО
    Потомок,
    Предок;

// 2. Удваиваем уровни (пример одного шага)
ВЫБРАТЬ
    Т1.Потомок КАК Потомок,
    ЕСТЬNULL(Т2.Предок, Т1.Предок) КАК Предок
ПОМЕСТИТЬ ВТ_Иерархия_Шаг1
ИЗ
    ВТ_Иерархия КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Иерархия КАК Т2
        ПО Т1.Предок = Т2.Потомок;

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

Решение 3: Использование встроенных возможностей иерархии СКД

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

  1. В наборе данных выбираем только элементы, у которых Особенный = ИСТИНА.
  2. В настройках отчета добавляем группировку по полю Ссылка с типом Иерархия.
  3. Платформа сама достроит дерево вверх до корня для каждого найденного элемента.

Однако этот метод покажет только те ветки дерева, где есть искомые элементы. Если ваша задача — вывести конкретный список групп из ТЗ и поставить напротив них «Да/Нет», этот способ потребует дополнительного левого соединения с полным списком групп.

Решение 4: Архитектурный подход (Денормализация)

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

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

В этом случае наш запрос превращается в элементарное соединение:


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

Такой подход гарантирует максимальную производительность, так как иерархия уже «развернута» в плоскую таблицу и поиск происходит по индексам.

Итоги и рекомендации

Выбор метода зависит от ваших условий:

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

← На главную