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