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