Как вывести дерево из таблицы значений в СКД

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

При разработке отчетов в системе компоновки данных (СКД) на платформе 1С программисты часто сталкиваются с необходимостью вывести иерархическую структуру (дерево). Классический пример — разузлование спецификаций номенклатуры, вывод структуры подчиненности организации или отображение каталога товаров с неограниченной вложенностью. Если данные выбираются прямым запросом из базы данных, то настроить иерархию в СКД достаточно просто. Но что делать, если исходные данные собираются программно в сложный массив и возвращаются в виде Таблицы значений (ТЗ)?

Существует распространенное заблуждение, что СКД не умеет строить рекурсивные связи «сама на себя» для внешних наборов данных (наборов данных типа «Объект»). На самом деле компоновщик отлично справляется с этой задачей непосредственно в оперативной памяти. В этой статье мы подробно разберем, как правильно настроить вывод полноценного дерева из таблицы значений штатными средствами СКД, а также решим проблему дублирования элементов с помощью синтетических ключей.

Главное заблуждение: «Для связи сам на себя в Наборе данных — объект нужны параметры»

Разработчики часто пытаются настроить рекурсивную связь наборов данных по аналогии с наборами данных типа «Запрос». В запросах для построения иерархии используется конструкция вида ГДЕ Ссылка В (&Родители), а в связях наборов указывается соответствующий параметр. Пытаясь перенести эту логику на таблицы значений, программисты сталкиваются с тем, что в наборе данных — объекте некуда передать этот параметр, и начинают изобретать сложные обходные пути (например, программную модификацию макета компоновки или ручную отрисовку дерева в табличном документе).

Как обстоит дело на самом деле: СКД умеет выполнять рекурсивное сопоставление строк («иерархический спуск») в оперативной памяти без использования каких-либо параметров связи или дополнительных запросов к БД. Достаточно просто правильно описать поля связи в интерфейсе конструктора настроек схемы компоновки данных.

Шаг 1. Подготовка исходной таблицы значений

Предположим, у нас есть программно сформированная таблица значений, которая содержит информацию о деталях и узлах изделия. Каждая строка содержит ссылку на саму себя через поле родителя. Давайте подготовим структуру нашей ТЗ:


// Создаем структуру таблицы значений
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДанных.Колонки.Добавить("РодительНоменклатуры", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТаблицаДанных.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));

При заполнении этой таблицы корневые элементы (самый верхний уровень дерева) будут иметь значение Справочник.Номенклатура.ПустаяСсылка() (или Неопределено / NULL) в колонке РодительНоменклатуры. Подчиненные элементы будут ссылаться на своего непосредственного родителя.

Шаг 2. Настройка схемы компоновки данных (СКД)

Для того чтобы СКД поняла структуру нашего дерева, выполним следующие шаги в конструкторе схемы компоновки:

  1. Создание набора данных: Добавим на вкладке «Наборы данных» новый Набор данных — объект. Назовем его НаборДанных1. В поле «Имя объекта содержащего данные» укажем имя нашей ТЗ, например, ТаблицаДанных.
  2. Добавление полей: Опишем поля набора данных в соответствии с колонками нашей ТЗ: Номенклатура, РодительНоменклатуры, Количество, Цена.
  3. Настройка связи наборов данных: Перейдем на вкладку «Связи наборов данных». Именно здесь происходит магия превращения плоской таблицы в дерево. Добавим новую строку со следующими параметрами:
    • Источник связи: НаборДанных1
    • Приемник связи: НаборДанных1 (указываем этот же набор данных!)
    • Выражение источник: Номенклатура
    • Выражение приемник: РодительНоменклатуры
    • Начальное значение связи: Это важнейший параметр, указывающий СКД, с каких элементов начинать раскрутку дерева. Здесь необходимо написать выражение, определяющее корневые элементы. Если у корневых элементов родитель пустой, пишем выражение: ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка).
  4. Настройка структуры отчета: На вкладке «Настройки» добавим группировку. Вместо обычной группировки по полю Номенклатура, нажмем на кнопку добавления группировки и выберем пункт «Новая иерархия...» (или выберем тип группировки Иерархия в свойствах уже созданной группировки). В качестве поля группировки укажем Номенклатура.

При программном выводе отчета достаточно передать подготовленную ТЗ в процессор компоновки:


ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаДанных", ТаблицаДанных);

// Стандартный вывод отчета с использованием внешнего набора данных
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки());

ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных);

ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(Результат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);

Шаг 3. Решение проблемы разузлования и дублирования элементов (Синтетические ключи)

На практике при решении задач разузлования (например, когда мы раскладываем спецификацию готовой продукции на материалы и полуфабрикаты) возникает серьезная проблема: один и тот же материал может входить в состав разных узлов на разных уровнях иерархии. Например, деталь «Болт» входит в узел «Рычаг», и этот же «Болт» входит в узел «Лапа».

Если мы настроим рекурсивную связь по полю Номенклатура, СКД не сможет корректно отобразить дерево. Возникнет логический конфликт: СКД попытается подчинить «Болт» одновременно и «Рычагу», и «Лапе», что приведет либо к «схлопыванию» ветвей дерева, либо к зацикливанию и бесконечной рекурсии.

Для решения этой проблемы мы должны использовать синтетические (технические) ключи связи вместо реальных ссылок номенклатуры. Давайте модернизируем нашу таблицу значений, добавив в нее служебные числовые колонки:


ТаблицаДанных = Новый ТаблицаЗначений;
// Бизнес-данные
ТаблицаДанных.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТаблицаДанных.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));

// Синтетические ключи для связи в СКД
ТаблицаДанных.Колонки.Добавить("ИДСтроки", Новый ОписаниеТипов("Число"));
ТаблицаДанных.Колонки.Добавить("ИДРодителя", Новый ОписаниеТипов("Число"));

При программном заполнении таблицы мы гарантируем уникальность поля ИДСтроки для каждой строки таблицы (например, используя простой автоинкрементный счетчик: 1, 2, 3 и т.д.). В поле ИДРодителя мы записываем значение ИДСтроки той строки, которая является родителем для текущей записи. У самых верхних (корневых) строк в поле ИДРодителя записываем значение 0.

Теперь перенастроим связи наборов данных в СКД на использование этих ключей:

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

Шаг 4. Альтернативный вариант: Разделение данных и структуры дерева

Иногда передавать всю структуру в одной перегруженной таблице неудобно или нерационально с точки зрения объема передаваемых данных. В таких случаях мы можем применить архитектурный паттерн разделения данных и иерархии, передав в СКД два разных набора данных типа «Объект»:

  1. НаборДанныхДанные — содержит плоские строки с показателями (Номенклатура, Количество, Цена).
  2. НаборДанныхИерархия — содержит только структуру подчиненности (Номенклатура, РодительНоменклатуры).

Связи настраиваются в два этапа:

Такой подход делает схему чище и позволяет повторно использовать таблицу связей для разных видов отчетов.

Шаг 5. Особенности расчета ресурсов (показателей) в иерархии

Важно помнить, как СКД рассчитывает итоги по ресурсам (например, полям Количество или Сумма) при построении дерева. Если ваша исходная таблица значений уже содержит готовые предрассчитанные агрегированные данные по узлам и родительским уровням, стандартное суммирование СКД (с использованием функции Сумма(ИмяПоля) на вкладке «Ресурсы») приведет к катастрофическому завышению итогов. Дочерние элементы приплюсуются к уже заполненным родительским значениям.

Варианты решения этой проблемы:

  1. Если ТЗ содержит предрассчитанные итоги, в описании ресурсов СКД для родительских уровней укажите расчет через функции Максимум(...) или Среднее(...) вместо Сумма(...). Это позволит вывести исходные цифры "как есть", без повторного сложения.
  2. Передавать в ТЗ данные только по конечным элементам дерева («листьям»), а расчет по узлам полностью доверить СКД. В таком случае стандартная функция Сумма(...) отработает корректно и автоматически соберет итоги снизу вверх по дереву.

Используя эти проверенные подходы, мы можем быстро, гибко и без написания громоздкого программного кода выводить сложные иерархические структуры в 1С, полностью контролируя процесс компоновки данных.

← На главную