В практике разработки на платформе 1С часто возникает стандартная задача: выбрать данные, входящие в определенную группу (используя оператор В ИЕРАРХИИ). Однако иногда мы сталкиваемся с обратной ситуацией. Представьте, что в регистре сведений в качестве измерения «Контрагент» могут быть записаны как конкретные элементы, так и целые группы. У нас есть массив конкретных элементов (ссылок), и нам нужно выбрать из регистра те записи, которые относятся либо прямо к этому элементу, либо к любой из его родительских групп. В штатном языке запросов нет оператора «ЯВЛЯЕТСЯ РОДИТЕЛЕМ ДЛЯ», поэтому нам приходится использовать обходные пути. Рассмотрим подробнее, как решить эту задачу несколькими способами.
Разберем самый надежный и простой в реализации метод. Если нам нужно проверить вхождение конкретного элемента в группы, указанные в регистре, мы можем заранее подготовить список всех «предков» этого элемента. Проанализируем ситуацию: объект в 1С имеет строго ограниченную цепочку родителей (вверх до корня). Мы можем собрать их в массив и передать этот массив в запрос.
Для этого выполним следующие шаги в программном коде:
Посмотрим на пример реализации такого алгоритма:
МассивРодителей = Новый Массив;
ТекущаяСсылка = ВыбранныйКонтрагент;
Пока ЗначениеЗаполнено(ТекущаяСсылка) Цикл
МассивРодителей.Добавить(ТекущаяСсылка);
ТекущаяСсылка = ТекущаяСсылка.Родитель;
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Сведения.Значение
|ИЗ
| РегистрСведений.НастройкиПоКонтрагентам КАК Сведения
|ГДЕ
| Сведения.Контрагент В (&МассивРодителей)";
Запрос.УстановитьПараметр("МассивРодителей", МассивРодителей);
Преимущество этого метода: он работает на любой версии платформы, крайне прост в отладке и не зависит от глубины вложенности справочника.
Если по каким-то причинам мы не можем подготовить массив заранее и нам нужно решить задачу исключительно внутри одного запроса, можно прибегнуть к соединению по родителям. Этот метод был предложен в обсуждении как быстрый, но «не универсальный» вариант. Мы ограничиваем максимальную глубину вложенности (например, до 5 или 10 уровней) и проверяем соответствие на каждом из них.
Проанализируем пример запроса, где мы проверяем вхождение товара в группу скидок:
ВЫБРАТЬ РАЗЛИЧНЫЕ
Номенклатура.Ссылка КАК Номенклатура,
СкрытыеСкидки.Скидка ИЗ Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СкрытыеСкидки КАК СкрытыеСкидки
ПО (СкрытыеСкидки.ТоварГруппа = Номенклатура.Ссылка
ИЛИ СкрытыеСкидки.ТоварГруппа = Номенклатура.Родитель
ИЛИ СкрытыеСкидки.ТоварГруппа = Номенклатура.Родитель.Родитель
ИЛИ СкрытыеСкидки.ТоварГруппа = Номенклатура.Родитель.Родитель.Родитель)
ГДЕ
Номенклатура.Ссылка В (&МассивТоваров)
Важно помнить, что такой подход требует ручного прописывания каждого уровня. Если в справочнике появится вложенность больше, чем предусмотрено в запросе, система перестанет находить настройки для самых глубоких элементов. Выясним причину популярности этого метода: он позволяет выполнить всё в одно обращение к БД без предварительных циклов в коде 1С.
Для сложных задач, когда элементов много и нужно массово сопоставить их с группами в регистре, лучше всего построить временную таблицу «Элемент — Все его предки». Это классическая задача на графы. Мы создаем временную таблицу, в которую помещаем пары (Ссылка, Ссылка), затем добавляем пары (Ссылка, Родитель), затем (Ссылка, Родитель.Родитель) и так далее.
Рассмотрим логику построения такой таблицы:
ВТ_Элементы.ОБЪЕДИНИТЬ ВСЕ формируем таблицу ВТ_Иерархия, где для каждого элемента из ВТ_Элементы указаны его родители на всех уровнях.ВТ_Иерархия с регистром сведений по полю «Родитель».Такой метод является наиболее производительным при работе с большими массивами данных, так как позволяет СУБД эффективно использовать индексы при соединении таблиц.
В процессе обсуждения темы выдвигалась гипотеза, что иерархию можно проверить простым сравнением: ГДЕ СсылкаРегистра <= &Элемент. Внимание: это утверждение ошибочно и опасно!
Разберем, почему это не работает:
Если вы работаете на платформе версии 8.3.20 и выше, вам доступен наиболее современный и «правильный» способ — рекурсивные запросы. С их помощью можно описать алгоритм подъема по дереву иерархии непосредственно в тексте запроса без ограничения глубины вложенности.
Используя конструкцию ВЫБРАТЬ ... ИЗ ... КАК ... ВМЕСТЕ ..., мы можем построить цепочку родителей динамически. Это избавляет от необходимости писать громоздкие конструкции с ИЛИ и позволяет базе данных максимально оптимизировать выполнение задачи.
Подведем итог: для разовых задач в небольших конфигурациях оптимально использовать Способ 1 (программный сбор родителей). Для высоконагруженных систем и массовой обработки данных следует использовать рекурсивные запросы или предварительное построение таблицы связей в памяти СУБД.