Как вывести один элемент иерархического справочника в нескольких группах запросом в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов, классификаторов номенклатуры, каталогов для интернет-магазинов или сложных систем аналитики разработчики 1С часто сталкиваются с задачей множественной иерархии (полииерархии). Классический пример: один и тот же товар (например, «Кабель HDMI») должен одновременно отображаться в группе «Проекторы и экраны» и в группе «Расходные материалы / Прочее».

Попытка решить эту задачу «в лоб» через стандартное предложение запроса ИТОГИ ПО ... ТОЛЬКО ИЕРАРХИЯ приводит к разочарованию. Разберем по шагам, почему возникает эта проблема, почему штатная иерархия запросов платформы не умеет дублировать элементы, и какие практические способы существуют для реализации полииерархии.

Почему стандартный запрос с ИТОГИ ПО ТОЛЬКО ИЕРАРХИЯ не работает?

Проанализируем структуру данных платформы 1С. Любой иерархический справочник в 1С устроен по принципу списка смежности (Adjacency List): в таблице базы данных у каждого элемента есть ровно одно поле Родитель (внутреннее системное поле ParentID). Элемент физически не может ссылаться на две разные родительские папки.

Когда мы пишем запрос следующего вида:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Товар,
    Номенклатура.Родитель КАК Родитель
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Ссылка = &Ссылка
ИТОГИ ПО
    Родитель ТОЛЬКО ИЕРАРХИЯ

Платформа 1С выполняет трансляцию запроса в серию обращений к СУБД, раскручивая дерево вверх строго по цепочке поля Родитель конкретной записи. Даже если мы искусственно соединим таблицы через ОБЪЕДИНИТЬ ВСЕ или свяжем элемент со списком дополнительных групп через табличную часть или регистр сведений, обход итогов по иерархии жестко опирается на физическую структуру таблицы справочника. Получить размножение веток с одним и тем же элементом внутри штатных итогов невозможно.

Рассмотрим рабочие альтернативные подходы к решению этой задачи.

Решение 1. Архитектурный паттерн: Независимый классификатор и Регистр сведений

Наиболее масштабируемый и правильный подход, который применяется в типовых конфигурациях (ERP, УТ 11, КА, УНФ для каталогов интернет-магазинов и товарных категорий) — это разделение физического хранения номенклатуры и ее классификации.

  1. Создаем отдельный справочник КатегорииНоменклатуры (с поддержкой иерархии групп и элементов или только групп). В нем выстраиваем дерево произвольной вложенности.
  2. Создаем непериодический независимый регистр сведений ПринадлежностьНоменклатурыКатегориям с измерениями:

    — Номенклатура (СправочникСсылка.Номенклатура)

    — Категория (СправочникСсылка.КатегорииНоменклатуры)

  3. Один товар связывается в регистре сведений с любым количеством категорий.

Теперь мы можем сформировать запрос, который соединяет категории и товары, группируя итоги по нужным разрезам:


ВЫБРАТЬ
    Связи.Категория КАК Категория,
    Связи.Номенклатура КАК Номенклатура
ИЗ
    РегистрСведений.ПринадлежностьНоменклатурыКатегориям КАК Связи
ИТОГИ ПО
    Категория ИЕРАРХИЯ,
    Номенклатура

В этом случае группировка Категория ИЕРАРХИЯ построит полноценное дерево по справочнику категорий, а на нижнем уровне (в детальных записях или подчиненных группировках) товар отобразится во всех назначенных ему папках.

Решение 2. Иерархия средствами Системы компоновки данных (СКД)

Если задача заключается в выводе отчета или динамического дерева, идеальным инструментом выступает СКД. Система компоновки данных умеет строить произвольную пользовательскую иерархию по любым рекурсивным связям наборов данных.

Разберем алгоритм настройки схемы компоновки данных:

  1. Создаем Набор данных — Запрос (основные данные):
    
    ВЫБРАТЬ
        Связи.Номенклатура КАК Номенклатура,
        Связи.Категория КАК Категория
    ИЗ
        РегистрСведений.ПринадлежностьНоменклатурыКатегориям КАК Связи
    
  2. Создаем второй Набор данных — Запрос (структура категорий):
    
    ВЫБРАТЬ
        Категории.Ссылка КАК Категория,
        Категории.Родитель КАК Родитель
    ИЗ
        Справочник.КатегорииНоменклатуры КАК Категории
    
  3. Настраиваем связи наборов данных:

    Переходим на вкладку Связи наборов данных. Связываем категорию из первого набора с категорией из второго.

    Для поля Родитель настраиваем связь саму на себя: указываем источник связи Категория, приемник Родитель и устанавливаем флажок Иерархия.

  4. Настраиваем структуру отчета:

    В настройках группировок добавляем группировку Категория (Иерархия), а внутрь нее помещаем детальные записи или группировку Номенклатура.

При таком подходе СКД сама построит полную ветку предков для каждой категории и поместит один и тот же товар в каждую из указанных ветвей.

Решение 3. Программная сборка в ДеревоЗначений

Если результат требуется получить в кодовой логике (например, для выгрузки на сайт, передачи в API или вывода в нестандартную форму) и СКД использовать нельзя, эффективнее всего собрать плоскую таблицу запросом и развернуть ее в дерево на встроенном языке 1С.

Рассмотрим практический пример. Сначала получаем плоскую выборку связей и всех задействованных групп:


ВЫБРАТЬ
    Связи.Номенклатура КАК Товар,
    Связи.Номенклатура.Наименование КАК ТоварНаименование,
    Связи.Категория КАК Группа
ИЗ
    РегистрСведений.ПринадлежностьНоменклатурыКатегориям КАК Связи

Затем строим результирующее ДеревоЗначений с помощью рекурсивного алгоритма заполнения родительских узлов:


// Создаем структуру результирующего дерева
ДеревоРезультат = Новый ДеревоЗначений;
ДеревоРезультат.Колонки.Добавить("Ссылка");
ДеревоРезультат.Колонки.Добавить("Наименование");

// Получаем плоскую таблицу связей
Выборка = Запрос.Выполнить().Выгрузить();

// Коллекция для быстрого поиска созданных строк дерева групп
КэшУзлов = Новый Соответствие;

Для Каждого Стр Из Выборка Цикл
    ТекущаяГруппа = Стр.Группа;
    
    // Получаем или создаем родительскую ветку для текущей группы
    РодительскаяСтрока = ПолучитьИлиСоздатьУзелДерева(ТекущаяГруппа, ДеревоРезультат, КэшУзлов);
    
    // Добавляем товар в конечную группу
    СтрокаТовара = РодительскаяСтрока.Строки.Добавить();
    СтрокаТовара.Ссылка = Стр.Товар;
    СтрокаТовара.Наименование = Стр.ТоварНаименование;
КонецЦикла;

Функция рекурсивного поиска и создания узлов дерева с учетом родителей:


Функция ПолучитьИлиСоздатьУзелДерева(ГруппаСсылка, ДеревоРезультат, КэшУзлов)
    
    Если ГруппаСсылка.Пустая() Или ГруппаСсылка = Неопределено Тогда
        Возврат ДеревоРезультат;
    КонецЕсли;
    
    НайденнаяСтрока = КэшУзлов[ГруппаСсылка];
    Если НайденнаяСтрока <> Неопределено Тогда
        Возврат НайденнаяСтрока;
    КонецЕсли;
    
    // Рекурсивно получаем родительский узел для текущей группы
    РодительУзла = ПолучитьИлиСоздатьУзелДерева(ГруппаСсылка.Родитель, ДеревоРезультат, КэшУзлов);
    
    // Создаем узел в соответствующей ветке дерева
    НоваяСтрока = РодительУзла.Строки.Добавить();
    НоваяСтрока.Ссылка = ГруппаСсылка;
    НоваяСтрока.Наименование = ГруппаСсылка.Наименование;
    
    КэшУзлов.Вставить(ГруппаСсылка, НоваяСтрока);
    
    Возврат НоваяСтрока;
КонецФункции

Решение 4. Паттерн Closure Table (Таблица транзитивного замыкания) для чистого SQL/Запроса

Если стоит жесткое ограничение сформировать структуру строго одним запросом без использования СКД и без циклов в коде 1С, применяют паттерн Closure Table. В базе создается вспомогательный регистр сведений ИерархияКатегорийТранзитивная со структурой:

  1. Предок (СправочникСсылка.Категории) — любой вышестоящий узел по дереву (включая саму группу).
  2. Потомок (СправочникСсылка.Категории) — подчиненный узел.
  3. Уровень (Число) — расстояние между предком и потомком.

Такой регистр заполняется один раз при записи элементов справочника категорий. Имея полностью развернутые пути в плоской таблице, мы можем соединить товар со всеми уровнями его категорий простым ЛЕВОЕ СОЕДИНЕНИЕ без рекурсии, получив развернутую иерархию любой глубины прямо в запросе.

Сводные выводы и рекомендации

  1. Штатная конструкция ИТОГИ ПО ... ТОЛЬКО ИЕРАРХИЯ работает строго по физическому реквизиту Родитель и не способна дублировать один элемент в разных ветках.
  2. Для построения отчетов с полииерархией используйте механизм иерархических связей наборов данных в СКД.
  3. Для хранения данных применяйте связку «Отдельный справочник категорий + Регистр сведений», не перегружая стандартный справочник Номенклатура лишними реквизитами.
  4. Для выгрузок и работы в коде используйте программное заполнение объекта ДеревоЗначений с кэшированием родительских узлов — это работает надежно, прозрачно и быстро.
← На главную