Запрос с выбором из справочника по иерархии
Условие
Составьте запрос для выборки одного значения из большого справочника с отбором по иерархии.
Дано:
- Справочник "Номенклатура" с иерархической структурой (группы и элементы)
- Нужно выбрать элементы, находящиеся в определённой группе и всех её подгруппах
Пример
Структура справочника:
Электроника/
├── Компьютеры/
│ ├── Ноутбуки/
│ │ ├── Lenovo
│ │ └── HP
│ └── Десктопы/
└── Телефоны/
При выборе группы "Компьютеры" должны вернуться: Lenovo, HP и все элементы из "Десктопы".
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Запрос с выбором по иерархии справочника
Рассмотрим различные подходы для выборки элементов из иерархического справочника и всех его подэлементов.
Архитектура справочника
Справочник "Номенклатура" имеет следующую структуру:
Справочник Номенклатура
Владелец: Номенклатура (самоссылка для иерархии)
Наименование: строка
Этозвуча: булево (флаг для разделения групп и элементов)
ТипНоменклатуры: перечисление (Товар, Услуга)
КонецСправочника
Способ 1: Запрос с ИЕРАРХИЕЙ (рекомендуется)
Самый эффективный способ для больших справочников:
// Функция для получения элементов из группы и всех подгрупп
Функция ПолучитьНоменклатуруПоГруппе(Группа, ВключатьГруппы = Ложь)
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование,
| Номенклатура.ЭтоГруппа,
| Номенклатура.Владелец,
| Номенклатура.ТипНоменклатуры
|ИЗ
| Справочник.Номенклатура.Иерархия(,ВсеУровни,Разрешенные) КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В Иерархии(Справочник.Номенклатура, @Группа)
| И НЕ(Номенклатура.ЭтоГруппа И НЕ @ВключатьГруппы)
|УПОРЯДОЧИТЬ ПО
| Номенклатура.Наименование";
Запрос.УстановитьПараметр("Группа", Группа);
Запрос.УстановитьПараметр("ВключатьГруппы", ВключатьГруппы);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
РезультатТаблица = Новый ТаблицаЗначений();
РезультатТаблица.Колонки.Добавить("Ссылка");
РезультатТаблица.Колонки.Добавить("Наименование");
РезультатТаблица.Колонки.Добавить("ЭтоГруппа");
РезультатТаблица.Колонки.Добавить("Владелец");
Пока Выборка.Следующий() Цикл
НоваяСтрока = РезультатТаблица.Добавить();
НоваяСтрока.Ссылка = Выборка.Ссылка;
НоваяСтрока.Наименование = Выборка.Наименование;
НоваяСтрока.ЭтоГруппа = Выборка.ЭтоГруппа;
НоваяСтрока.Владелец = Выборка.Владелец;
КонецЦикла;
Возврат РезультатТаблица;
КонецФункции
Синтаксис ключевых конструкций:
.Иерархия(, ВсеУровни, Разрешенные)— получает иерархию справочникаВ Иерархии(Справочник, Значение)— проверяет вхождение в иерархиюВсеУровни— включит все уровни иерархииРазрешенные— учитывает права доступа
Способ 2: Рекурсивное объединение (CTE)
Для более сложных выборок и фильтраций:
// Запрос с рекурсивным CTE для получения элементов группы
Функция ПолучитьНоменклатуруРекурсивно(Группа)
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование,
| Номенклатура.ЭтоГруппа,
| Номенклатура.ТипНоменклатуры,
| 0 КАК Уровень
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = @Группа
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| НоменклатураДочерняя.Ссылка,
| НоменклатураДочерняя.Наименование,
| НоменклатураДочерняя.ЭтоГруппа,
| НоменклатураДочерняя.ТипНоменклатуры,
| ДанныеИерархии.Уровень + 1
|ИЗ
| СписокНоменклатуры КАК ДанныеИерархии
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК НоменклатураДочерняя
| ПО ДанныеИерархии.Ссылка = НоменклатураДочерняя.Владелец
|ГДЕ
| НЕ НоменклатураДочерняя.ЭтоГруппа";
Запрос.УстановитьПараметр("Группа", Группа);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
// Обработка результатов
Возврат Выборка;
КонецФункции
Способ 3: Программный обход иерархии
Когда запрос недостаточен, используем программный обход:
// Функция для программного обхода иерархии в глубину
Процедура ПолучитьВсеЭлементыРекурсивно(Группа, РезультатТаблица)
// Получаем прямых потомков
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование,
| Номенклатура.ЭтоГруппа
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Владелец = @Владелец
|УПОРЯДОЧИТЬ ПО
| Номенклатура.Наименование";
Запрос.УстановитьПараметр("Владелец", Группа);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Добавляем элемент или группу
НоваяСтрока = РезультатТаблица.Добавить();
НоваяСтрока.Ссылка = Выборка.Ссылка;
НоваяСтрока.Наименование = Выборка.Наименование;
НоваяСтрока.ЭтоГруппа = Выборка.ЭтоГруппа;
// Если это группа, рекурсивно получаем её элементы
Если Выборка.ЭтоГруппа Тогда
ПолучитьВсеЭлементыРекурсивно(Выборка.Ссылка, РезультатТаблица);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Обёртка для удобного использования
Функция ПолучитьВсеЭлементыГруппы(Группа, ТолькоЭлементы = Истина)
РезультатТаблица = Новый ТаблицаЗначений();
РезультатТаблица.Колонки.Добавить("Ссылка");
РезультатТаблица.Колонки.Добавить("Наименование");
РезультатТаблица.Колонки.Добавить("ЭтоГруппа");
ПолучитьВсеЭлементыРекурсивно(Группа, РезультатТаблица);
Если ТолькоЭлементы Тогда
// Удаляем группы из результата
ДляУдаления = Новый Массив();
Для й = 0 По РезультатТаблица.Количество() - 1 Цикл
Если РезультатТаблица[й].ЭтоГруппа Тогда
ДляУдаления.Добавить(й);
КонецЕсли;
КонецЦикла;
Для й = ДляУдаления.Количество() - 1 По 0 Шаг -1 Цикл
РезультатТаблица.Удалить(ДляУдаления[й]);
КонецЦикла;
КонецЕсли;
Возврат РезультатТаблица;
КонецФункции
Способ 4: Оптимизированный запрос с фильтрацией
Для выборки элементов с дополнительными условиями:
// Запрос для получения элементов с фильтром по типу
Функция ПолучитьНоменклатуруПоГруппеИТипу(Группа, ТипНоменклатуры)
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование,
| Номенклатура.ТипНоменклатуры,
| Номенклатура.ЭтоГруппа
|ИЗ
| Справочник.Номенклатура.Иерархия(,ВсеУровни,Разрешенные) КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В Иерархии(Справочник.Номенклатура, @Группа)
| И НЕ Номенклатура.ЭтоГруппа
| И Номенклатура.ТипНоменклатуры = @ТипНоменклатуры
|УПОРЯДОЧИТЬ ПО
| Номенклатура.Наименование";
Запрос.УстановитьПараметр("Группа", Группа);
Запрос.УстановитьПараметр("ТипНоменклатуры", ТипНоменклатуры);
Результат = Запрос.Выполнить();
Возврат Результат;
КонецФункции
Способ 5: Запрос с подсчётом количества элементов
Для аналитики по группам:
// Запрос для получения статистики по группам
Функция ПолучитьСтатистикуПоГруппам()
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ПодчинённаяНоменклатура.Ссылка) КАК КоличествоЭлементов
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК ПодчинённаяНоменклатура
| ПО ПодчинённаяНоменклатура.Ссылка В Иерархии(
| Справочник.Номенклатура,
| Номенклатура.Ссылка
| )
|ГДЕ
| Номенклатура.ЭтоГруппа = Истина
|СГРУППИРОВАТЬ ПО
| Номенклатура.Ссылка,
| Номенклатура.Наименование
|УПОРЯДОЧИТЬ ПО
| КоличествоЭлементов УБЫВ";
Результат = Запрос.Выполнить();
Возврат Результат;
КонецФункции
Использование в управляемой форме
// Процедура для заполнения дерева элементов
Процедура ЗаполнитьДеревоЭлементов(Группа)
ЭтаФорма.ЭлементыГруппы.Очистить();
Результат = ПолучитьНоменклатуруПоГруппе(Группа);
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
ЭлементДерева = ЭтаФорма.ЭлементыГруппы.Элементы.Добавить();
ЭлементДерева.Наименование = Выборка.Наименование;
ЭлементДерева.Ссылка = Выборка.Ссылка;
ЭлементДерева.ЭтоГруппа = Выборка.ЭтоГруппа;
КонецЦикла;
КонецПроцедуры
Сравнение способов
Способ 1 (Иерархия):
- Плюсы: быстро, оптимизировано СУБД
- Минусы: синтаксис сложный
- Использование: основной способ для больших справочников
Способ 2 (CTE):
- Плюсы: полная контроль, хорошо масштабируется
- Минусы: может быть медленнее на больших данных
- Использование: сложные условия выборки
Способ 3 (Программный обход):
- Плюсы: гибкость, простота логики
- Минусы: медленно на больших справочниках
- Использование: малые справочники, специальная логика
Оптимизация для больших справочников
// Кэширование результатов
ПеременнаяКэша = Новое Соответствие();
Функция ПолучитьВКэше(Ключ)
Если ПеременнаяКэша.Получить(Ключ) = Неопределено Тогда
РезультатЗапроса = ПолучитьНоменклатуруПоГруппе(Ключ);
ПеременнаяКэша.Вставить(Ключ, РезультатЗапроса);
КонецЕсли;
Возврат ПеременнаяКэша.Получить(Ключ);
КонецФункции
Рекомендуется использовать Способ 1 для большинства случаев, так как он оптимален по производительности и поддерживается СУБД.