В стандартной практике разработки на платформе 1С:Предприятие мы часто используем конструкцию В ИЕРАРХИИ. Она отлично работает, когда нам нужно найти все элементы, входящие в определенную группу (поиск «вниз»). Однако иногда возникает обратная задача: проверить, является ли конкретный узел (параметр запроса) предком для выбранного элемента (поиск «вверх»).
Стандартный оператор языка запросов Элемент В ИЕРАРХИИ (&Группа) не предназначен для работы в формате &Параметр В ИЕРАРХИИ (Элемент). Давайте разберем, почему это так, и проанализируем основные способы решения этой технической проблемы.
Стандартный поиск «вниз» эффективно оптимизирован платформой. При выполнении запроса 1С использует либо специальные индексы иерархии в СУБД, либо строит условия, охватывающие всех потомков. Когда же нам нужно проверить «вверх», мы сталкиваемся с тем, что у одного элемента может быть только один непосредственный родитель, но целая цепочка предков до самого корня. В языке запросов 1С отсутствуют рекурсивные запросы (аналоги WITH RECURSIVE в SQL), что вынуждает нас искать обходные пути.
Рассмотрим самый простой, но часто критикуемый метод — получение всех родителей элемента во временный массив перед выполнением основного запроса. Несмотря на известную фразу о том, что «запросы в цикле — это плохо», для одиночного объекта этот метод может быть самым быстрым в реализации.
Проанализируем алгоритм:
В.Пример реализации на языке 1С:
МассивПредков = Новый Массив;
ТекущийРодитель = ОбъектСсылка.Родитель;
Пока ЗначениеЗаполнено(ТекущийРодитель) Цикл
МассивПредков.Добавить(ТекущийРодитель);
ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;
// Теперь используем МассивПредков в запросе
Запрос.Текст = "ВЫБРАТЬ ... ГДЕ &ИскомаяГруппа В (&МассивПредков)";
Преимущества: простота реализации, не требует изменения структуры метаданных.
Недостатки: низкая производительность при массовой обработке данных (например, в динамическом списке или сложном отчете).
Если ваша задача — часто проверять принадлежность элемента к определенной крупной ветке справочника (например, «Договоры аренды» или «Договоры купли-продажи»), рассмотрим метод денормализации. Выясним причину популярности этого метода в высоконагруженных системах: он позволяет перенести вычисления из момента выполнения запроса в момент записи объекта.
Разберем шаги реализации:
Договоры новый реквизит ГоловнойРодитель (тип СправочникСсылка.Договоры).ПередЗаписью пропишем логику поиска самого верхнего узла иерархии.Теперь наш запрос будет выглядеть максимально элегантно и работать мгновенно:
ВЫБРАТЬ
Договоры.Ссылка
ИЗ
Справочник.Договоры КАК Договоры
ГДЕ
Договоры.ГоловнойРодитель = &ПараметрГруппаДоговоров
Как отмечалось в обсуждении, вместо реквизита можно использовать Регистр сведений, если вы не хотите менять структуру самого справочника или если у элемента может быть несколько «смысловых» корней.
Посмотрим на ситуацию под другим углом. В некоторых конфигурациях используется иерархия групп и элементов, а коды справочников строятся иерархически. Если мы можем гарантировать, что код элемента содержит коды всех его родителей (так называемый Materialized Path), то задача решается текстовым сравнением.
В запросе это реализуется через оператор ПОДОБНО. Рассмотрим пример:
ВЫБРАТЬ
Договоры.Ссылка
ИЗ
Справочник.Договоры КАК Договоры
ГДЕ
Договоры.Код ПОДОБНО &КодГруппы + "%"
Важно помнить, что этот метод специфичен и сильно зависит от настроек нумерации в конкретной базе данных.
Если нам нужно решить задачу строго в рамках одного запроса без предварительной подготовки данных в коде, мы можем использовать последовательные левые соединения таблицы самой с собой по полю Родитель. Проанализируем этот подход: он ограничен фиксированным количеством уровней вложенности.
Рассмотрим пример запроса для поиска предков до 3-го уровня:
ВЫБРАТЬ
Договоры.Ссылка,
ЕСТЬNULL(Договоры.Родитель, ЗНАЧЕНИЕ(Справочник.Договоры.ПустаяСсылка)) КАК Родитель1,
ЕСТЬNULL(Договоры.Родитель.Родитель, ЗНАЧЕНИЕ(Справочник.Договоры.ПустаяСсылка)) КАК Родитель2,
ЕСТЬNULL(Договоры.Родитель.Родитель.Родитель, ЗНАЧЕНИЕ(Справочник.Договоры.ПустаяСсылка)) КАК Родитель3
ПОМЕСТИТЬ ВТ_ВсеПредки
ИЗ
Справочник.Договоры КАК Договоры
ГДЕ
Договоры.Ссылка = &ЭлементДляПроверки;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ИСТИНА
ИЗ
ВТ_ВсеПредки КАК Предки
ГДЕ
&ИскомаяГруппа В (Предки.Родитель1, Предки.Родитель2, Предки.Родитель3)
Хотя этот метод кажется громоздким, он позволяет в рамках одного обращения к БД проверить принадлежность к иерархии на определенную глубину.
Если ваша задача реализуется в рамках отчета, то СКД — ваш лучший союзник. В СКД встроен мощный механизм работы с иерархией. Мы можем настроить связи наборов данных, указав в качестве начального условия текущий элемент, а в качестве связи — отношение «Сам к своему родителю».
Алгоритм настройки в СКД:
Родитель, Приемник связи: Ссылка.СКД самостоятельно выполнит итерации и соберет все уровни предков, избавив разработчика от написания циклов или сложных соединений.
Выяснив все доступные варианты, мы можем подвести итог:
ГоловногоРодителя).Помните, что архитектурно правильное решение всегда зависит от контекста. Если иерархия справочника небольшая (2–3 уровня), даже «наивный» обход в запросе через несколько точек (Родитель.Родитель) может быть оправдан своей простотой.