← Назад к вопросам

Запрос с выбором из справочника по иерархии

2.0 Middle🔥 131 комментариев
#Запросы и оптимизация#Объекты метаданных

Условие

Составьте запрос для выборки одного значения из большого справочника с отбором по иерархии.

Дано:

  • Справочник "Номенклатура" с иерархической структурой (группы и элементы)
  • Нужно выбрать элементы, находящиеся в определённой группе и всех её подгруппах

Пример

Структура справочника:

Электроника/
├── Компьютеры/
│   ├── Ноутбуки/
│   │   ├── Lenovo
│   │   └── HP
│   └── Десктопы/
└── Телефоны/

При выборе группы "Компьютеры" должны вернуться: Lenovo, HP и все элементы из "Десктопы".

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Запрос с выбором по иерархии справочника

Рассмотрим различные подходы для выборки элементов из иерархического справочника и всех его подэлементов.

Архитектура справочника

Справочник "Номенклатура" имеет следующую структуру:

Справочник Номенклатура
  Владелец: Номенклатура (самоссылка для иерархии)
  Наименование: строка
  Этозвуча: булево (флаг для разделения групп и элементов)
  ТипНоменклатуры: перечисление (Товар, Услуга)
КонецСправочника

Способ 1: Запрос с ИЕРАРХИЕЙ (рекомендуется)

Самый эффективный способ для больших справочников:

// Функция для получения элементов из группы и всех подгрупп
Функция ПолучитьНоменклатуруПоГруппе(Группа, ВключатьГруппы = Ложь)
    
    Запрос = Новый Запрос();
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Номенклатура.Ссылка,
    |    Номенклатура.Наименование,
    |    Номенклатура.ЭтоГруппа,
    |    Номенклатура.Владелец,
    |    Номенклатура.ТипНоменклатуры
    |ИЗ
    |    Справочник.Номенклатура.Иерархия(,ВсеУровни,Разрешенные) КАК Номенклатура
    |ГДЕ
    |    Номенклатура.Ссылка В Иерархии(Справочник.Номенклатура, @Группа)
    |    И НЕ(Номенклатура.ЭтоГруппа И НЕ @ВключатьГруппы)
    |УПОРЯДОЧИТЬ ПО
    |    Номенклатура.Наименование";
    
    Запрос.УстановитьПараметр("Группа", Группа);
    Запрос.УстановитьПараметр("ВключатьГруппы", ВключатьГруппы);
    
    Результат = Запрос.Выполнить();
    Выборка = Результат.Выбрать();
    
    РезультатТаблица = Новый ТаблицаЗначений();
    РезультатТаблица.Колонки.Добавить("Ссылка");
    РезультатТаблица.Колонки.Добавить("Наименование");
    РезультатТаблица.Колонки.Добавить("ЭтоГруппа");
    РезультатТаблица.Колонки.Добавить("Владелец");
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = РезультатТаблица.Добавить();
        НоваяСтрока.Ссылка = Выборка.Ссылка;
        НоваяСтрока.Наименование = Выборка.Наименование;
        НоваяСтрока.ЭтоГруппа = Выборка.ЭтоГруппа;
        НоваяСтрока.Владелец = Выборка.Владелец;
    КонецЦикла;
    
    Возврат РезультатТаблица;
    
КонецФункции

Синтаксис ключевых конструкций:

  • .Иерархия(, ВсеУровни, Разрешенные) — получает иерархию справочника
  • В Иерархии(Справочник, Значение) — проверяет вхождение в иерархию
  • ВсеУровни — включит все уровни иерархии
  • Разрешенные — учитывает права доступа

Способ 2: Рекурсивное объединение (CTE)

Для более сложных выборок и фильтраций:

// Запрос с рекурсивным CTE для получения элементов группы
Функция ПолучитьНоменклатуруРекурсивно(Группа)
    
    Запрос = Новый Запрос();
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Номенклатура.Ссылка,
    |    Номенклатура.Наименование,
    |    Номенклатура.ЭтоГруппа,
    |    Номенклатура.ТипНоменклатуры,
    |    0 КАК Уровень
    |ИЗ
    |    Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |    Номенклатура.Ссылка = @Группа
    |
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    |    НоменклатураДочерняя.Ссылка,
    |    НоменклатураДочерняя.Наименование,
    |    НоменклатураДочерняя.ЭтоГруппа,
    |    НоменклатураДочерняя.ТипНоменклатуры,
    |    ДанныеИерархии.Уровень + 1
    |ИЗ
    |    СписокНоменклатуры КАК ДанныеИерархии
    |    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК НоменклатураДочерняя
    |        ПО ДанныеИерархии.Ссылка = НоменклатураДочерняя.Владелец
    |ГДЕ
    |    НЕ НоменклатураДочерняя.ЭтоГруппа";
    
    Запрос.УстановитьПараметр("Группа", Группа);
    
    Результат = Запрос.Выполнить();
    Выборка = Результат.Выбрать();
    
    // Обработка результатов
    Возврат Выборка;
    
КонецФункции

Способ 3: Программный обход иерархии

Когда запрос недостаточен, используем программный обход:

// Функция для программного обхода иерархии в глубину
Процедура ПолучитьВсеЭлементыРекурсивно(Группа, РезультатТаблица)
    
    // Получаем прямых потомков
    Запрос = Новый Запрос();
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Номенклатура.Ссылка,
    |    Номенклатура.Наименование,
    |    Номенклатура.ЭтоГруппа
    |ИЗ
    |    Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |    Номенклатура.Владелец = @Владелец
    |УПОРЯДОЧИТЬ ПО
    |    Номенклатура.Наименование";
    
    Запрос.УстановитьПараметр("Владелец", Группа);
    
    Результат = Запрос.Выполнить();
    Выборка = Результат.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        // Добавляем элемент или группу
        НоваяСтрока = РезультатТаблица.Добавить();
        НоваяСтрока.Ссылка = Выборка.Ссылка;
        НоваяСтрока.Наименование = Выборка.Наименование;
        НоваяСтрока.ЭтоГруппа = Выборка.ЭтоГруппа;
        
        // Если это группа, рекурсивно получаем её элементы
        Если Выборка.ЭтоГруппа Тогда
            ПолучитьВсеЭлементыРекурсивно(Выборка.Ссылка, РезультатТаблица);
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

// Обёртка для удобного использования
Функция ПолучитьВсеЭлементыГруппы(Группа, ТолькоЭлементы = Истина)
    
    РезультатТаблица = Новый ТаблицаЗначений();
    РезультатТаблица.Колонки.Добавить("Ссылка");
    РезультатТаблица.Колонки.Добавить("Наименование");
    РезультатТаблица.Колонки.Добавить("ЭтоГруппа");
    
    ПолучитьВсеЭлементыРекурсивно(Группа, РезультатТаблица);
    
    Если ТолькоЭлементы Тогда
        // Удаляем группы из результата
        ДляУдаления = Новый Массив();
        Для й = 0 По РезультатТаблица.Количество() - 1 Цикл
            Если РезультатТаблица[й].ЭтоГруппа Тогда
                ДляУдаления.Добавить(й);
            КонецЕсли;
        КонецЦикла;
        
        Для й = ДляУдаления.Количество() - 1 По 0 Шаг -1 Цикл
            РезультатТаблица.Удалить(ДляУдаления[й]);
        КонецЦикла;
    КонецЕсли;
    
    Возврат РезультатТаблица;
    
КонецФункции

Способ 4: Оптимизированный запрос с фильтрацией

Для выборки элементов с дополнительными условиями:

// Запрос для получения элементов с фильтром по типу
Функция ПолучитьНоменклатуруПоГруппеИТипу(Группа, ТипНоменклатуры)
    
    Запрос = Новый Запрос();
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Номенклатура.Ссылка,
    |    Номенклатура.Наименование,
    |    Номенклатура.ТипНоменклатуры,
    |    Номенклатура.ЭтоГруппа
    |ИЗ
    |    Справочник.Номенклатура.Иерархия(,ВсеУровни,Разрешенные) КАК Номенклатура
    |ГДЕ
    |    Номенклатура.Ссылка В Иерархии(Справочник.Номенклатура, @Группа)
    |    И НЕ Номенклатура.ЭтоГруппа
    |    И Номенклатура.ТипНоменклатуры = @ТипНоменклатуры
    |УПОРЯДОЧИТЬ ПО
    |    Номенклатура.Наименование";
    
    Запрос.УстановитьПараметр("Группа", Группа);
    Запрос.УстановитьПараметр("ТипНоменклатуры", ТипНоменклатуры);
    
    Результат = Запрос.Выполнить();
    Возврат Результат;
    
КонецФункции

Способ 5: Запрос с подсчётом количества элементов

Для аналитики по группам:

// Запрос для получения статистики по группам
Функция ПолучитьСтатистикуПоГруппам()
    
    Запрос = Новый Запрос();
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Номенклатура.Ссылка,
    |    Номенклатура.Наименование,
    |    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ПодчинённаяНоменклатура.Ссылка) КАК КоличествоЭлементов
    |ИЗ
    |    Справочник.Номенклатура КАК Номенклатура
    |    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК ПодчинённаяНоменклатура
    |        ПО ПодчинённаяНоменклатура.Ссылка В Иерархии(
    |            Справочник.Номенклатура,
    |            Номенклатура.Ссылка
    |        )
    |ГДЕ
    |    Номенклатура.ЭтоГруппа = Истина
    |СГРУППИРОВАТЬ ПО
    |    Номенклатура.Ссылка,
    |    Номенклатура.Наименование
    |УПОРЯДОЧИТЬ ПО
    |    КоличествоЭлементов УБЫВ";
    
    Результат = Запрос.Выполнить();
    Возврат Результат;
    
КонецФункции

Использование в управляемой форме

// Процедура для заполнения дерева элементов
Процедура ЗаполнитьДеревоЭлементов(Группа)
    
    ЭтаФорма.ЭлементыГруппы.Очистить();
    
    Результат = ПолучитьНоменклатуруПоГруппе(Группа);
    Выборка = Результат.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        ЭлементДерева = ЭтаФорма.ЭлементыГруппы.Элементы.Добавить();
        ЭлементДерева.Наименование = Выборка.Наименование;
        ЭлементДерева.Ссылка = Выборка.Ссылка;
        ЭлементДерева.ЭтоГруппа = Выборка.ЭтоГруппа;
    КонецЦикла;
    
КонецПроцедуры

Сравнение способов

Способ 1 (Иерархия):

  • Плюсы: быстро, оптимизировано СУБД
  • Минусы: синтаксис сложный
  • Использование: основной способ для больших справочников

Способ 2 (CTE):

  • Плюсы: полная контроль, хорошо масштабируется
  • Минусы: может быть медленнее на больших данных
  • Использование: сложные условия выборки

Способ 3 (Программный обход):

  • Плюсы: гибкость, простота логики
  • Минусы: медленно на больших справочниках
  • Использование: малые справочники, специальная логика

Оптимизация для больших справочников

// Кэширование результатов
ПеременнаяКэша = Новое Соответствие();

Функция ПолучитьВКэше(Ключ)
    Если ПеременнаяКэша.Получить(Ключ) = Неопределено Тогда
        РезультатЗапроса = ПолучитьНоменклатуруПоГруппе(Ключ);
        ПеременнаяКэша.Вставить(Ключ, РезультатЗапроса);
    КонецЕсли;
    Возврат ПеременнаяКэша.Получить(Ключ);
КонецФункции

Рекомендуется использовать Способ 1 для большинства случаев, так как он оптимален по производительности и поддерживается СУБД.