Как сформировать кросс-отчет вручную в 1С без использования СКД?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

Создание классического отчета вида «шахматка» (кросс-таблицы), где по вертикали выводятся строки (например, контрагенты), по горизонтали — колонки (номенклатура), а на их пересечении — числовые показатели (количество, сумма), в большинстве типовых конфигураций реализуется через систему компоновки данных (СКД). Однако в учебных задачах, при жестких требованиях к оптимизации или при интеграции в нестандартные печатные формы разработчикам требуется формировать такие отчеты программно. Разберем, как устроен механизм вывода двумерных отчетов в 1С и как избежать типичных ошибок со сдвигом колонок.

В чем заключается главная сложность кросс-отчетов

Основная проблема при формировании кросс-таблицы вручную заключается в нерегулярности данных. У одного контрагента в документах может быть приобретен весь перечень номенклатуры, а у другого — всего одна позиция. Если попытаться выводить данные напрямую в цикле обхода документов, таблица неизбежно «поплывет»:

  1. Если при выводе строки контрагента вызывать метод Вывести(), платформа добавляет новую строку таблицы снизу от текущей позиции курсора.
  2. Если вызывать метод Присоединить(), платформа прикрепляет ячейки справа к последней сформированной строке.
  3. Если у какого-то контрагента товар отсутствует, а разработчик просто пропускает итерацию, следующая ячейка с количеством сдвинется влево и окажется под чужой колонкой номенклатуры.

Поэтому вывод кросс-отчета напрямую из выборки документов в одном цикле невозможен. Процесс обязательно должен делиться на подготовку структуры данных и последовательный поэтапный вывод.

Организация макета для кросс-отчета

Для корректного формирования сетки макет табличного документа размечается с использованием пересечения вертикальных и горизонтальных секций. Выделим в макете следующие ключевые области:

  1. Шапка|Начало — левый верхний угол отчета (заголовок для строк, например «Контрагент»).
  2. Шапка|Колонка — повторяющаяся по горизонтали ячейка шапки, в которую выводится название номенклатуры.
  3. Строка|Начало — ячейка начала строки данных, куда выводится наименование контрагента.
  4. Строка|Колонка — ячейка пересечения, куда помещается количество или сумма.

Пошаговый алгоритм формирования кросс-таблицы

Проанализируем правильную последовательность действий при формировании отчета вручную:

  1. Сбор всех уникальных колонок: формируем список (массив или колонку таблицы значений) всей номенклатуры, которая фигурирует в периоде отчета.
  2. Сбор всех уникальных строк: формируем список всех контрагентов.
  3. Подготовка таблицы пересечений: сохраняем данные о продажах в структуру или таблицу значений с колонками Контрагент, Номенклатура, Количество. Для быстрой выборки в таблице значений обязательно создаем индекс по колонкам Контрагент, Номенклатура.
  4. Вывод шапки: выводим область Шапка|Начало через метод Вывести(), затем в цикле по массиву номенклатуры прикрепляем области Шапка|Колонка методом Присоединить().
  5. Вывод строк данных: во внешнем цикле обходим контрагентов. Для каждого контрагента выводим Строка|Начало методом Вывести(). Во внутреннем цикле обходим все элементы массива номенклатуры. На каждой итерации ищем данные в подготовленной таблице: если товар покупался — выводим количество, если нет — выводим пустую ячейку. Область Строка|Колонка присоединяется методом Присоединить() на каждом шаге цикла по колонкам.

Программный пример реализации кросс-отчета

Посмотрим на практический пример кода, который формирует таблицу без смещения колонок:


// 1. Подготовка макета и результирующего документа
ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("Макет");

ОбластьШапкаНачало  = Макет.ПолучитьОбласть("Шапка|Начало");
ОбластьШапкаКолонка = Макет.ПолучитьОбласть("Шапка|Колонка");
ОбластьСтрокаНачало = Макет.ПолучитьОбласть("Строка|Начало");
ОбластьСтрокаКолонка= Макет.ПолучитьОбласть("Строка|Колонка");

// 2. Сбор данных через объектную модель
ТЗ_Данные = Новый ТаблицаЗначений;
ТЗ_Данные.Колонки.Добавить("Контрагент");
ТЗ_Данные.Колонки.Добавить("Номенклатура");
ТЗ_Данные.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

Выборка = Документы.РеализацияТоваровУслуг.Выбрать(ПериодНачало, ПериодКонец);
Пока Выборка.Следующий() Цикл
    Если Не Выборка.Проведен Тогда
        Продолжить;
    КонецЕсли;
    
    Для Каждого СтрТЧ Из Выборка.Товары Цикл
        НовСтр = ТЗ_Данные.Добавить();
        НовСтр.Контрагент = Выборка.Контрагент;
        НовСтр.Номенклатура = СтрТЧ.Номенклатура;
        НовСтр.Количество = СтрТЧ.Количество;
    КонецЦикла;
КонецЦикла;

// Сворачиваем данные для устранения дублей
ТЗ_Данные.Свернуть("Контрагент, Номенклатура", "Количество");
ТЗ_Данные.Индексы.Добавить("Контрагент, Номенклатура");

// Получаем уникальные списки колонок и строк
ТЗ_Колонки = ТЗ_Данные.Скопировать();
ТЗ_Колонки.Свернуть("Номенклатура");

ТЗ_Строки = ТЗ_Данные.Скопировать();
ТЗ_Строки.Свернуть("Контрагент");

// 3. Вывод шапки отчета
ТабДок.Вывести(ОбластьШапкаНачало);
Для Каждого СтрКолонка Из ТЗ_Колонки Цикл
    ОбластьШапкаКолонка.Параметры.Номенклатура = СтрКолонка.Номенклатура;
    ТабДок.Присоединить(ОбластьШапкаКолонка);
КонецЦикла;

// 4. Построчный вывод строк отчета
ПараметрыПоиска = Новый Структура("Контрагент, Номенклатура");
Для Каждого СтрКонтрагент Из ТЗ_Строки Цикл
    // Начинаем новую строку
    ОбластьСтрокаНачало.Параметры.Контрагент = СтрКонтрагент.Контрагент;
    ТабДок.Вывести(ОбластьСтрокаНачало);
    
    ПараметрыПоиска.Контрагент = СтрКонтрагент.Контрагент;
    
    // Вложенный цикл по всем колонкам отчета
    Для Каждого СтрКолонка Из ТЗ_Колонки Цикл
        ПараметрыПоиска.Номенклатура = СтрКолонка.Номенклатура;
        НайденныеСтроки = ТЗ_Данные.НайтиСтроки(ПараметрыПоиска);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            ОбластьСтрокаКолонка.Параметры.Количество = НайденныеСтроки[0].Количество;
        Иначе
            ОбластьСтрокаКолонка.Параметры.Количество = 0;
        КонецЕсли;
        
        // Присоединяем ячейку ОБЯЗАТЕЛЬНО на каждой итерации
        ТабДок.Присоединить(ОбластьСтрокаКолонка);
    КонецЦикла;
КонецЦикла;

ТабДок.Показать();

Альтернативные способы реализации кросс-таблиц

Если требуется обойтись без ручной расстановки вызовов Вывести() и Присоединить(), можно воспользоваться другими встроенными механизмами платформы 1С:

  1. Прямая адресация ячеек по номерам: заполнять табличный документ можно через координаты ячеек, обращаясь к свойству ТабДок.Область(НомерСтроки, НомерКолонки).Текст = Значение. В таком варианте разработчик заранее знает координаты каждого контрагента и номенклатуры, что исключает сдвиги сетки.
  2. Использование объекта ПостроительОтчета: в платформе 1С существует объект ПостроительОтчета, который был основным инструментом создания отчетов до появления СКД. Он позволяет настроить кросс-таблицу через свойства ИзмеренияСтроки и ИзмеренияКолонки, формируя шахматку автоматически.
  3. Элемент СводнаяТаблица: объект табличного документа, предназначенный для динамического многомерного анализа данных без ручного программирования циклов.

Оптимизация производительности

Выборка документов методом Документы.РеализацияТоваровУслуг.Выбрать() поднимает в оперативную память весь объект документа со всеми табличными частями. В реальных базах такой подход приводит к колоссальным затратам памяти и медленной работе. В рабочей практике данные для отчетов всегда следует получать через объект Запрос к регистрам накопления, а не к документам напрямую.

Кроме того, при использовании ТаблицаЗначений.НайтиСтроки() во вложенном цикле создание индекса через команду Таблица.Индексы.Добавить() критически важно: без индекса платформа выполняет полный линейный перебор таблицы на каждом шаге, что при больших объемах данных полностью парализует работу системы.

← На главную