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