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