При разработке сложных аналитических отчетов в 1С на базе Системы компоновки данных (СКД) перед разработчиками нередко возникает задача визуализации данных по принципу «таблица в таблице». Например, когда в рамках одной строки или ячейки основной кросс-таблицы требуется отобразить детализированную подчиненную таблицу со своей собственной структурой строк и колонок.
В этой статье мы подробно разберем, почему стандартные механизмы СКД накладывают жесткие ограничения на такую структуру, выясним причины их возникновения и рассмотрим четыре практических способа реализации этой задачи.
Для начала проанализируем поведение платформы при попытке напрямую настроить вложенную таблицу. Если мы обратимся к дереву настроек СКД, то обнаружим следующие особенности поведения системы:
Вложенный отчет внутрь строк или колонок элемента структуры Таблица (кросс-таблицы). В контекстном меню для группировок таблицы данный пункт меню будет неактивен.Таблица внутрь другого элемента типа Таблица на одном уровне компоновки.Вложенный отчет для вывода детальных данных в циклах строк родительского отчета крайне неэффективно. На уровне СУБД это порождает проблему «N+1 запросов» (Nested Loops), когда для каждой строки основного отчета платформа выполняет отдельный запрос к базе данных. На реальных базах данных это мгновенно приведет к критическому падению производительности и зависанию сервера.Посмотрим, как обойти данные ограничения и предоставить пользователю удобный, наглядный и быстро работающий отчет.
Если специфика бизнес-требований позволяет отказаться от классической кросс-таблицы на верхнем уровне, мы можем использовать гибридную схему. Если корневой элемент отчета представить в виде обычной линейной Группировки, то внутри нее СКД отлично поддерживает вложенные объекты:
Склад или Контрагент).Таблица или Вложенный отчет.В результате при компоновке отчета для каждого элемента главной группировки будет сформирована и выведена полноценная подчиненная таблица со своими собственными колонками, строками и ресурсами.
Разберем, как получить структуру связанных данных без использования вложенных отчетов, нагружающих систему. Для этого задействуем механизм связей на уровне источников данных СКД:
НаборДанных1 (основные показатели, например, общие продажи по подразделениям) и НаборДанных2 (детализация продаж по видам номенклатуры).НаборДанных1, приемником — НаборДанных2. В качестве условия связи укажем ключевое поле (например, Подразделение).Иногда под «таблицей в таблице» подразумевается необходимость просто показать перечень подчиненных элементов прямо в ячейке основной строки, не раздувая отчет по вертикали. Выясним, как решить эту задачу изящно с помощью агрегатных функций ресурса:
Рассмотрим пример, когда в строке основной таблицы нужно вывести все виды использованных кормов или список связанных договоров. Для этого:
СоединитьСтроки(...).СоединитьСтроки(ВидКорма, ", ", "-"), где вторым параметром передается разделитель строк.При таком подходе все детальные записи будут перечислены через запятую внутри одной ячейки строки родительской таблицы, сохраняя общую монолитную структуру кросс-таблицы.
В ситуациях, когда требуется сложная визуализация (например, отрисовка рамки вложенной таблицы внутри ячеек, объединение ячеек по сложным условиям или динамическое управление видимостью), стандартный конструктор СКД не справится. В таких случаях мы используем программное управление процессом вывода.
Разберем по шагам алгоритм программной обработки:
ПриКомпоновкеРезультата() и установив параметр СтандартнаяОбработка = Ложь.ДеревоЗначений.ТабличныйДокумент с применением ручной группировки строк через методы НачатьГруппуСтрок() и ЗакончитьГруппуСтрок().Посмотрим на пример реализации кода в модуле объекта отчета:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Формируем макет компоновки данных
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
// Инициализируем процессор компоновки
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
// Выводим результат компоновки в дерево значений
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВДеревоЗначений;
ДеревоРезультата = Новый ДеревоЗначений;
ПроцессорВывода.УстановитьОбъект(ДеревоРезультата);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
// Очищаем результирующий документ и запускаем ручной вывод
ДокументРезультат.Очистить();
ВывестиДеревоДанныхВДокумент(ДеревоРезультата.Строки, ДокументРезультат);
КонецПроцедуры
Далее реализуем вспомогательную процедуру ВывестиДеревоДанныхВДокумент, которая будет рекурсивно обходить дерево значений и выводить строки в табличный документ, создавая визуальный эффект вложенных таблиц:
Процедура ВывестиДеревоДанныхВДокумент(СтрокиДерева, ТабДок, Уровень = 0)
Для Каждого СтрокаДерева Из СтрокиДерева Цикл
// Для группировок верхнего уровня открываем интерактивную группу строк
Если Уровень = 0 Тогда
ТабДок.НачатьГруппуСтрок(СтрокаДерева.ПараметрГруппировки, Ложь);
КонецЕсли;
// Получаем предопределенную область макета из нашего отчета
ОбластьСтроки = ПолучитьМакетСтроки(СтрокаДерева, Уровень);
ТабДок.Вывести(ОбластьСтроки);
// Обрабатываем подчиненные строки (нашу "внутреннюю" таблицу)
Если СтрокаДерева.Строки.Количество() > 0 Тогда
ВывестиДеревоДанныхВДокумент(СтрокаДерева.Строки, ТабДок, Уровень + 1);
КонецЕсли;
// Закрываем группу строк для верхнего уровня
Если Уровень = 0 Тогда
ТабДок.ЗакончитьГруппуСтрок();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В функции ПолучитьМакетСтроки разработчик может гибко управлять оформлением ячеек: программно устанавливать границы, менять шрифт, цвет фона и выводить рамки таблиц для детальных записей, имитируя классическое вложение таблиц.
Таким образом, мы рассмотрели все ключевые способы решения задачи. Выбор конкретного метода зависит от требований к визуализации отчета и объема обрабатываемых данных.