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