При разработке отчетов на системе компоновки данных (СКД) программисты часто сталкиваются с жесткими требованиями заказчиков к оформлению. Типичная сложная задача — вывести в рамках одной горизонтальной таблицы разнородные данные (например, плоскую таблицу документов реализации и иерархическое дерево документов поступления), которые логически объединены общим аналитическим разрезом (например, Объект или Договор).
Если решать эту задачу «в лоб» через простое левое или правое соединение в запросе, возникнет проблема декартова произведения (перемножения строк). На каждый документ реализации СКД продублирует строки поступлений, из-за чего итоговые суммы исказятся, а отчет превратится в нечитаемую кашу. Полноценно настроить такую структуру стандартными интерактивными средствами конструктора СКД тоже не всегда удается, так как система требует строгих связей на уровне каждой строки.
В этой статье мы подробно разберем несколько профессиональных подходов к решению этой проблемы: от полной программной постобработки (гибридного метода) до использования современных возможностей СУБД и тонкой настройки связей наборов данных в СКД.
---
Этот метод часто называют компромиссным. Мы не отказываемся от СКД полностью — ведь она предоставляет удобные механизмы для наложения отборов, работы с параметрами и условного оформления. Вместо этого мы используем СКД исключительно для получения и компоновки «сырых» данных, после чего выгружаем их в дерево значений и выводим в итоговый табличный документ с помощью собственного макета.
Разберем этот процесс по шагам.
Шаг 1. Настройка схемы СКД
В схеме компоновки данных мы настраиваем два набора данных-запроса (один для реализации, второй для поступлений) и связываем их по полю Объект. На вкладке «Настройки» создаем структуру, которая возвращает данные в иерархическом виде (например, группировка по полю Объект, а внутри нее — детальные записи).
Шаг 2. Программное выполнение компоновки и обход дерева
В модуле отчета (или в модуле менеджера) перехватываем процедуру вывода. Нам необходимо программно выполнить СКД, выгрузить результат в ДеревоЗначений, а затем обойти полученное дерево и вывести его в ТабличныйДокумент по ячейкам нашего макета.
Ниже представлен пример кода, реализующий этот алгоритм:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// 1. Инициализируем схему и настройки
СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
// 2. Компонуем макет данных
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
// 3. Выполняем компоновку
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
// 4. Выгружаем данные в Дерево Значений
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ДеревоРезультата = Новый ДеревоЗначений;
ПроцессорВывода.УстановитьОбъект(ДеревоРезультата);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
// 5. Выводим данные в Табличный Документ по нашему макету
ВывестиДанныеВТабличныйДокумент(ДокументРезультат, ДеревоРезультата);
КонецПроцедуры
Процедура ВывестиДанныеВТабличныйДокумент(ТабДок, ДеревоРезультата)
ТабДок.Очистить();
Макет = ПолучитьМакет("МакетРучной"); // Наш собственный макет оформления
// Выводим шапку отчета
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ТабДок.Вывести(ОбластьШапка);
// Обходим первый уровень группировки — Объекты
Для Каждого СтрокаОбъект Из ДеревоРезультата.Строки Цикл
ОбластьОбъект = Макет.ПолучитьОбласть("Объект");
ОбластьОбъект.Параметры.Заполнить(СтрокаОбъект);
ТабДок.Вывести(ОбластьОбъект);
// Получаем подчиненные коллекции для реализации и поступлений
// (Предполагается, что в запросе мы добавили разделитель "ТипДанных")
ПараметрыОтбораРеализаций = Новый Структура("ТипДанных", "Реализация");
СтрокиРеализации = СтрокаОбъект.Строки.НайтиСтроки(ПараметрыОтбораРеализаций, Истина);
ПараметрыОтбораПоступлений = Новый Структура("ТипДанных", "Поступление");
СтрокиПоступления = СтрокаОбъект.Строки.НайтиСтроки(ПараметрыОтбораПоступлений, Истина);
// Определяем, какая таблица длиннее, чтобы вывести их параллельно
МаксКоличествоСтрок = Макс(СтрокиРеализации.Количество(), СтрокиПоступления.Количество());
Для Индекс = 0 По МаксКоличествоСтрок - 1 Цикл
ОбластьСтрокаДанных = Макет.ПолучитьОбласть("СтрокаДанных");
// Заполняем левую часть строки (Реализация)
Если Индекс < СтрокиРеализации.Количество() Тогда
ТекущаяРеализация = СтрокиРеализации[Индекс];
ОбластьСтрокаДанных.Параметры.РеализацияДокумент = ТекущаяРеализация.Документ;
ОбластьСтрокаДанных.Параметры.РеализацияСумма = ТекущаяРеализация.Сумма;
Иначе
ОбластьСтрокаДанных.Параметры.РеализацияДокумент = "";
ОбластьСтрокаДанных.Параметры.РеализацияСумма = 0;
КонецЕсли;
// Заполняем правую часть строки (Поступление)
Если Индекс < СтрокиПоступления.Количество() Тогда
ТекущееПоступление = СтрокиПоступления[Индекс];
ОбластьСтрокаДанных.Параметры.ПоступлениеДокумент = ТекущееПоступление.Документ;
ОбластьСтрокаДанных.Параметры.ПоступлениеСумма = ТекущееПоступление.Сумма;
Иначе
ОбластьСтрокаДанных.Параметры.ПоступлениеДокумент = "";
ОбластьСтрокаДанных.Параметры.ПоступлениеСумма = 0;
КонецЕсли;
ТабДок.Вывести(ОбластьСтрокаДанных);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Этот способ гарантирует 100% контроль над геометрией колонок и выводом строк, однако лишает пользователя возможности самостоятельно менять структуру отчета (менять местами колонки, добавлять новые пользовательские группировки) через настройки отчета.
---
Если вы хотите сохранить отчет полностью на СКД, чтобы работали стандартные расшифровки, условное оформление и пользовательские настройки структуры, необходимо решить проблему соединения данных на уровне запроса. Для этого отлично подходит метод сквозной нумерации с использованием оконных функций, доступных в платформе 1С:Предприятие начиная с версии 8.3.15.
Идея заключается в том, чтобы пронумеровать строки внутри каждого Объекта отдельно для реализации и отдельно для поступлений. Затем мы соединяем эти таблицы по условию равенства объектов и равенства номеров строк.
Рассмотрим структуру такого запроса:
ВЫБРАТЬ
Реализация.Объект КАК Объект,
Реализация.Ссылка КАК ДокументРеализации,
Реализация.СуммаДокумента КАК СуммаРеализации,
ROW_NUMBER() OVER (PARTITION BY Реализация.Объект ORDER BY Реализация.МоментВремени) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_Реализации
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Поступление.Объект КАК Объект,
Поступление.Ссылка КАК ДокументПоступления,
Поступление.СуммаДокумента КАК СуммаПоступления,
ROW_NUMBER() OVER (PARTITION BY Поступление.Объект ORDER BY Поступление.МоментВремени) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_Поступления
ИЗ
Документ.ПоступлениеТоваровУслуг КАК Поступление
ГДЕ
Поступление.Проведен;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ЕСТЬNULL(ВТ_Реализации.Объект, ВТ_Поступления.Объект) КАК Объект,
ВТ_Реализации.ДокументРеализации КАК ДокументРеализации,
ВТ_Реализации.СуммаРеализации КАК СуммаРеализации,
ВТ_Поступления.ДокументПоступления КАК ДокументПоступления,
ВТ_Поступления.СуммаПоступления КАК СуммаПоступления
ИЗ
ВТ_Реализации КАК ВТ_Реализации
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Поступления КАК ВТ_Поступления
ПО ВТ_Реализации.Объект = ВТ_Поступления.Объект
И ВТ_Реализации.НомерСтроки = ВТ_Поступления.НомерСтроки
Почему это работает:
Благодаря оконной функции ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) мы получаем уникальный порядковый номер для каждого документа внутри конкретного объекта. При полном соединении по двум полям (Объект и НомерСтроки) первая строка реализации сопоставится с первой строкой поступления, вторая со второй и так далее. Если у одного объекта документов реализации больше, чем поступлений, то в колонках поступлений выведутся значения NULL, не вызывая дублирования и умножения данных.
---
Если использование оконных функций невозможно (например, база данных работает на старой версии платформы или СУБД не поддерживает эти функции), мы можем настроить параллельные группировки в СКД через связь наборов данных.
Для этого в схеме компоновки данных мы создаем три независимых набора данных:
ВЫБРАТЬ Ссылка КАК Объект ИЗ Справочник.ОбъектыСтроительства
Настройка связей наборов данных:
На вкладке «Связи наборов данных» мы добавляем две строки:
НаборДанных_Объекты, Приемник: НаборДанных_Реализации. Условие связи: Объект = Объект.НаборДанных_Объекты, Приемник: НаборДанных_Поступления. Условие связи: Объект = Объект.Настройка структуры отчета:
Для того чтобы вывести данные параллельно в рамках одного объекта, мы используем элемент структуры «Таблица»:
Объект.Объект создаем две параллельные (находящиеся на одном уровне подчинения) детальные группировки. Чтобы СКД разнесла их по разным колонкам, мы создаем вычисляемое поле-разделитель (например, ВидДокумента со значениями "Реализации" и "Поступления") и используем его в колонках таблицы.---
Если таблицы реализаций и поступлений логически независимы и имеют абсолютно разную структуру, которую сложно уместить в плоскую таблицу, мы можем применить механизм вложенных отчетов в СКД.
Посмотрим, как это реализовать:
Объект добавляем два вложенных отчета: «Реализация» и «Поступления».Объект родительского отчета через вкладку «Вложенные отчеты» в настройках схемы компоновки.Такой подход позволяет внутри одного отчета комбинировать абсолютно разнородную структуру таблиц и деревьев без необходимости писать сложный программный код.
---
Иногда жесткие требования заказчика к "параллельности" данных можно обойти, предложив альтернативное визуальное представление. Например, объединить детальные записи документов в рамках одной строки в виде списка, разделенного переносом строки.
Для этого в СКД можно использовать механизм вычисляемых полей совместно с агрегатной функцией ВычислитьВыражениеСГруппировкойМассив.
Разберем пример настройки вычисляемого поля:
СписокРеализаций.СоединитьСтроки(ВычислитьВыражениеСГруппировкойМассив("Представление(Ссылка)", "Ссылка")).
Этот метод позволяет сохранить структуру отчета простой и линейной, при этом выводя всю сопутствующую детальную информацию в одной аккуратной ячейке.
---
Проанализируем все рассмотренные варианты, чтобы выбрать наиболее подходящий под ваши задачи:
Рекомендация: Если у вас современная платформа 1С (8.3.15 и выше), в первую очередь попробуйте реализовать задачу через Метод 2 (Оконные функции). Это наиболее изящный способ, сохраняющий все преимущества платформенного механизма компоновки данных. Если же геометрия колонок макета выходит за рамки стандартных возможностей ячеек СКД, используйте Метод 1 (Гибридный метод) с выводом через дерево значений.