При разработке сложных отчетов или аналитических панелей в 1С программисты часто сталкиваются с задачей отображения разнородных данных в виде единой сквозной иерархии. Классический пример — построение структуры компании, где на верхнем уровне находится Организация, ниже располагаются Подразделения организации (которые сами по себе могут иметь сложную вложенную иерархию), а на самом нижнем уровне (или даже вперемешку с подразделениями) находятся конкретные позиции Штатного расписания.
Сложность заключается в том, что стандартная иерархия 1С работает «из коробки» только в пределах одного справочника. Нам же необходимо объединить данные из двух абсолютно разных таблиц: иерархического справочника СправочникСсылка.ПодразделенияОрганизаций и подчиненного, но неиерархического справочника СправочникСсылка.ШтатноеРасписание.
Многие разработчики по привычке начинают решать эту задачу «в лоб»: пишут сложный запрос, выгружают плоскую ТаблицаЗначений, а затем с помощью медленных рекурсивных функций в цикле программно перекладывают строки в ДеревоЗначений. Мы подробно разберем, почему этот путь неоптимален, и покажем, как элегантно решить эту задачу с помощью Системы компоновки данных (СКД), получив готовое дерево значений напрямую из процессора вывода.
Выясним причину, почему стандартные подходы не работают, и разберем по шагам правильную технологию реализации.
---
Прежде чем перейти к правильному решению, проанализируем ситуацию с ручной сборкой дерева на клиенте или сервере. Написание собственных алгоритмов рекурсивного обхода таблиц значений имеет несколько критических недостатков:
Всех этих проблем можно легко избежать, если переложить построение связей и расчет итогов на мощный встроенный движок — СКД.
---
Чтобы СКД смогла построить дерево по разнородным объектам, нам необходимо представить данные в виде единой плоской таблицы связей. В теории баз данных эта структура называется списком смежности (Adjacency List). Каждая строка такой таблицы должна содержать информацию о текущем узле и его непосредственном родителе.
Спроектируем запрос, который объединит данные по организациям, подразделениям и штатному расписанию. Основная идея заключается в приведении ключевых полей к единому составному типу данных. Мы выделим два главных поля:
ЭлементСхемы — текущая сущность (может быть Организацией, Подразделением или Позицией штатного расписания).Родитель — вышестоящая сущность (для подразделения это вышестоящее подразделение или организация, для штатного расписания — подразделение, к которому относится позиция).Рассмотрим подробнее пример такого запроса:
ВЫБРАТЬ
Организации.Ссылка КАК ЭлементСхемы,
НЕОПРЕДЕЛЕНО КАК Родитель,
"Организация" КАК ТипУзла
ИЗ
Справочник.Организации КАК Организации
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Подразделения.Ссылка КАК ЭлементСхемы,
ВЫБОР
КОГДА Подразделения.Родитель = ЗНАЧЕНИЕ(Справочник.ПодразделенияОрганизаций.ПустаяСсылка)
ТОГДА Подразделения.Владелец
ИНАЧЕ Подразделения.Родитель
КОНЕЦ КАК Родитель,
"Подразделение" КАК ТипУзла
ИЗ
Справочник.ПодразделенияОрганизаций КАК Подразделения
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ШтатноеРасписание.Ссылка КАК ЭлементСхемы,
ШтатноеРасписание.Подразделение КАК Родитель,
"Позиция" КАК ТипУзла
ИЗ
Справочник.ШтатноеРасписание КАК ШтатноеРасписание
Обратите внимание на логику ветвления в поле Родитель для подразделений. Если у подразделения нет вышестоящего подразделения (оно находится на самом верхнем уровне), то его родителем объявляется владелец — Организация. У позиций штатного расписания родителем всегда будет являться подразделение, к которому привязана позиция. У самой организации родитель отсутствует (равен НЕОПРЕДЕЛЕНО), что делает её корнем нашего дерева.
---
Теперь нам нужно объяснить СКД, как эту плоскую таблицу развернуть в иерархическое дерево. Для этого используется механизм связей наборов данных. Мы свяжем наш набор данных сам с собой рекурсивным способом.
Если вы настраиваете схему в конструкторе СКД, перейдите на вкладку «Связи наборов данных» и добавьте строку со следующими параметрами:
НаборДанныхИерархия).НаборДанныхИерархия).Родитель.ЭлементСхемы.НЕОПРЕДЕЛЕНО.В случае, когда схема компоновки данных формируется или дорабатывается программно (как это часто бывает в типовых конфигурациях вроде ЗУП), мы можем описать эту связь непосредственно в коде. Посмотрим на пример кода добавления иерархической связи:
// Создаем программную связь набора данных с самим собой для построения иерархии
Связь = СхемаКомпоновкиДанных.СвязиНаборовДанных.Добавить();
Связь.ИсточникСвязи = "НаборДанныхИерархия";
Связь.ПриемникСвязи = "НаборДанныхИерархия";
Связь.ВыражениеИсточник = "Родитель";
Связь.ВыражениеПриемник = "ЭлементСхемы";
Связь.Параметр = "ИерархияЭлемент";
Связь.НачальноеЗначениеСвязи = ПредопределенноеЗначение("Справочник.Организации.ПустаяСсылка");
После настройки связей обязательно перейдите в настройки варианта отчета в СКД. Вместо обычной группировки по полю ЭлементСхемы добавьте группировку с типом «Иерархия». Именно эта настройка заставит компоновщик использовать иерархические связи при выводе.
---
Когда схема и связи настроены, нам остается лишь получить готовый результат. Вместо вывода в стандартный табличный документ, мы укажем в качестве приемника объект ДеревоЗначений. СКД выполнит всю тяжелую работу по формированию иерархии и автоматически заполнит объект правильной структурой.
Разберем по шагам готовый шаблон кода для выгрузки:
// Инициализируем компоновщик макета
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
// Важно: используем генератор макета для коллекции значений!
МакетКомпоновки = КомпоновщикМакета.Выполнить(
СхемаКомпоновкиДанных,
КомпоновщикНастроек.Настройки,
ДанныеРасшифровки, ,
Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений")
);
// Инициализируем процессор компоновки данных
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
// Создаем пустое дерево значений, куда будет осуществлен вывод
ДеревоРезультат = Новый ДеревоЗначений;
// Инициализируем процессор вывода в коллекцию значений
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ПроцессорВывода.УстановитьОбъект(ДеревоРезультат);
// Запускаем процесс генерации дерева
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
// На выходе получаем готовое ДеревоЗначений с полностью построенной иерархией!
Благодаря использованию класса ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений, платформа самостоятельно создаст необходимые строки дерева, подчинит их друг другу и перенесет все выбранные ресурсы и реквизиты в соответствующие колонки.
---
Хотя этот механизм работает невероятно эффективно, существует несколько подводных камней, о которых важно помнить при промышленной разработке:
ЭлементСхемы и Родитель).Применяя данный подход, мы полностью избавляемся от необходимости писать сложные рекурсивные функции разбора таблиц на встроенном языке 1С. Система компоновки данных берет на себя всю рутину по построению иерархии, расчету промежуточных итогов и формированию итогового ДеревоЗначений с высокой скоростью и надежностью.