В практике разработки на платформе 1С:Предприятие 8 стандартный оператор В ИЕРАРХИИ используется для проверки того, входит ли элемент (или группа) в состав какой-либо папки. Однако часто возникает обратная задача: в таблице базы данных (например, в регистре сведений) указана группа, а нам нужно найти все записи, которые относятся к конкретному элементу, находящемуся глубоко внутри этой группы. В этой ситуации стандартная конструкция &Параметр В ИЕРАРХИИ(ПолеТаблицы) выдает синтаксическую ошибку, так как оператор ожидает ссылку или массив ссылок в скобках, а не поле запроса.
В данной статье мы разберем, как обойти это ограничение, проанализируем механику работы иерархии и рассмотрим несколько эффективных способов решения задачи «обратной иерархии».
Представим ситуацию: у нас есть справочник Склады с многоуровневой иерархией. В регистре сведений ДействиеСкидокНаценокНаСкладах хранятся настройки скидок. В поле Склад этого регистра может быть указан как конкретный склад-элемент, так и целая группа складов (например, «Оптовые склады»).
Нам необходимо получить все скидки, действующие для конкретного Склада 102. Логика должна быть следующей:
Склад указан непосредственно сам Склад 102.Склад указана группа-родитель (например, Оптовые склады район 1).Проблема в том, что обычное условие ГДЕ &Склад В ИЕРАРХИИ(Склад) не работает. Нам нужно «развернуть» логику и найти всех предков нашего склада.
Проанализируем самый надежный и производительный метод. Мы можем за один короткий запрос к справочнику получить список всех вышестоящих групп для нашего целевого склада и поместить их во временную таблицу. Платформа 1С позволяет использовать оператор В ИЕРАРХИИ «наоборот» именно при обращении к самому справочнику.
Рассмотрим по шагам, как это реализовать в коде запроса:
// ШАГ 1: Получаем все родительские группы и сам элемент во временную таблицу
ВЫБРАТЬ
Склады.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_ВсеРодители
ИЗ
Справочник.Склады КАК Склады
ГДЕ
&СкладПараметр В ИЕРАРХИИ(Склады.Ссылка);
// ШАГ 2: Основной запрос к регистру сведений
ВЫБРАТЬ
Скидки.СкидкаНаценка,
Скидки.Склад
ИЗ
РегистрСведений.ДействиеСкидокНаценокНаСкладах.СрезПоследних(&Дата, ) КАК Скидки
ГДЕ
Скидки.Склад В (ВЫБРАТЬ Ссылка ИЗ ВТ_ВсеРодители)
И Скидки.Статус = &Статус;
Почему это работает? В первом запросе условие &СкладПараметр В ИЕРАРХИИ(Склады.Ссылка) заставляет систему найти все записи в справочнике, для которых наш склад является потомком. В результат попадет сам склад, его непосредственный родитель, родитель родителя и так далее до самого корня. Имея этот список во временной таблице ВТ_ВсеРодители, мы просто фильтруем наш основной регистр по этому списку.
Если по каким-то причинам мы не хотим использовать менеджер временных таблиц (например, в простом динамическом списке), мы можем перенести логику в подзапрос прямо внутри условия ГДЕ. Это решение выглядит компактнее, но функционально идентично первому.
Разберем пример такого запроса:
ВЫБРАТЬ
ДействиеСкидок.СкидкаНаценка КАК Скидка,
ДействиеСкидок.Склад КАК СкладРегистра
ИЗ
РегистрСведений.ДействиеСкидокНаценокНаСкладах.СрезПоследних(&Дата, ) КАК ДействиеСкидок
ГДЕ
ДействиеСкидок.Склад В (
ВЫБРАТЬ
СпрСклады.Ссылка
ИЗ
Справочник.Склады КАК СпрСклады
ГДЕ
&СкладПараметр В ИЕРАРХИИ(СпрСклады.Ссылка)
)
И ДействиеСкидок.Статус = &Статус;
Проанализируем этот подход: СУБД сначала выполнит вложенный запрос, сформирует список ссылок-родителей, а затем сопоставит их с полем Склад в регистре. Это эффективное решение для иерархических справочников любой вложенности.
В некоторых случаях, например, при построении сложных отчетов в СКД или когда нужно вывести дополнительные данные о родителях, удобно использовать левое соединение. Посмотрим, как это выглядит:
ВЫБРАТЬ
Скидки.СкидкаНаценка,
Скидки.Склад КАК СкладИзРегистра
ИЗ
РегистрСведений.ДействиеСкидокНаценокНаСкладах.СрезПоследних(&Дата, ) КАК Скидки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Склады КАК СпрСклады
ПО Скидки.Склад = СпрСклады.Ссылка
ГДЕ
&СкладПараметр В ИЕРАРХИИ(СпрСклады.Ссылка);
В данном примере мы соединяем таблицу скидок со справочником складов. Условие В ИЕРАРХИИ в секции ГДЕ фильтрует уже соединенную таблицу. Важно понимать, что этот метод может быть чуть менее производительным на огромных таблицах, чем использование временной таблицы, так как соединение происходит до фильтрации по иерархии (хотя оптимизатор SQL-запросов часто нивелирует эту разницу).
Рассмотрим несколько моментов, которые помогут избежать ошибок при реализации:
СправочникСсылка.Склады. Если тип составной, использование индексов при проверке иерархии может быть затруднено, что замедлит запрос. В таких случаях полезно использовать функцию ВЫРАЗИТЬ(Скидки.Склад КАК Справочник.Склады).&СкладПараметр будет пустым, запрос может вернуть непредсказуемый результат (или вообще ничего не вернуть в зависимости от настроек). Рекомендуем добавить проверку на пустую ссылку перед выполнением запроса.В ИЕРАРХИИ транзитивен по своей природе.Мы выяснили, что для решения задачи «обратной иерархии» не нужно пытаться заставить оператор В ИЕРАРХИИ работать с полем запроса в качестве родителя. Вместо этого следует использовать принцип получения списка всех предков целевого элемента.
Наиболее предпочтительным методом является использование временной таблицы (Решение №1), так как оно разделяет логику поиска структуры и логику выбора данных из целевого регистра, что упрощает отладку и поддержку кода. Теперь вы можете гибко настраивать скидки, права доступа или любые другие параметры, которые должны наследоваться «сверху вниз» от групп к конкретным элементам в 1С.