Как сформировать динамический кросс-отчет без СКД в 1С 8.3?

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

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

Принцип формирования динамических колонок в табличном документе

Обычный отчет строится построчно: мы получаем горизонтальную полосу из макета и выводим ее методом ТабДок.Вывести(). В случае динамического отчета количество колонок заранее неизвестно (например, в строки необходимо вывести контрагентов, а в колонки — номенклатуру, которая была им отгружена).

Для решения этой задачи макет размечается пересекающимися именованными областями:

  1. По горизонтали выделяются строки: Шапка, Строка, Подвал.
  2. По вертикали выделяются колонки: ФиксированнаяКолонка (для наименований строк), КолонкаДанных (динамическая область для колонок), ИтогКолонка.

Ячейки на пересечении горизонтальных и вертикальных областей получаются с помощью оператора вертикальной черты: Макет.ПолучитьОбласть("Строка|КолонкаДанных"). Вывод первой ячейки в строке осуществляется методом ТабДок.Вывести(), а все последующие колонки этой же строки добавляются вправо методом ТабДок.Присоединить().

Анализ распространенных ошибок начинающих разработчиков

Посмотрим на типичные проблемы, с которыми сталкиваются при попытке сформировать кросс-таблицу вручную:

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

Пошаговый алгоритм создания кросс-отчета

Разберем правильную последовательность действий для построения надежного отчета.

Шаг 1. Подготовка данных с помощью запроса.

Вместо обхода документов в цикле напишем запрос с группировкой, который вернет уникальные пары контрагентов и номенклатуры, а также итоговые суммы или количества:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|   Реализация.Контрагент КАК Контрагент,
|   РеализацияТовары.Номенклатура КАК Номенклатура,
|   СУММА(РеализацияТовары.Количество) КАК Количество,
|   СУММА(РеализацияТовары.Сумма) КАК Сумма
|ИЗ
|   Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
|   ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
|       ПО РеализацияТовары.Ссылка = Реализация.Ссылка
|ГДЕ
|   Реализация.Проведен = ИСТИНА
|   И Реализация.Дата МЕЖДУ &ДатаНач И &ДатаКон
|СГРУППИРОВАТЬ ПО
|   Реализация.Контрагент,
|   РеализацияТовары.Номенклатура";

Запрос.УстановитьПараметр("ДатаНач", ПериодНачало);
Запрос.УстановитьПараметр("ДатаКон", ПериодКонец);

ТаблицаДанных = Запрос.Выполнить().Выгрузить();

Шаг 2. Формирование списков строк и колонок.

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


ТаблицаДанных.Индексы.Добавить("Контрагент, Номенклатура");

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

// Получаем массив уникальных строк
ТаблицаСтрок = ТаблицаДанных.Скопировать();
ТаблицаСтрок.Свернуть("Контрагент");
ТаблицаСтрок.Сортировать("Контрагент Возр");

Шаг 3. Вывод шапки отчета.

Сначала выводим левый верхний угол (заголовок строк), затем в цикле присоединяем динамические колонки:


// Левый верхний угол
ОбластьУгол = Макет.ПолучитьОбласть("Шапка|ФиксированнаяКолонка");
ТабДок.Вывести(ОбластьУгол);

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

Шаг 4. Вывод строк данных с обязательным заполнением пустых ячеек.

Обойдем каждого контрагента, выведем его наименование и для каждой номенклатуры проверим наличие продаж:


ОбластьСтрокаНачало = Макет.ПолучитьОбласть("Строка|ФиксированнаяКолонка");
ОбластьСтрокаДанные = Макет.ПолучитьОбласть("Строка|КолонкаДанных");

Отбор = Новый Структура("Контрагент, Номенклатура");

Для Каждого СтрКонтрагент Из ТаблицаСтрок Цикл
    // Выводим начало строки
    ОбластьСтрокаНачало.Параметры.Контрагент = СтрКонтрагент.Контрагент;
    ТабДок.Вывести(ОбластьСтрокаНачало);
    
    Отбор.Контрагент = СтрКонтрагент.Контрагент;
    
    // Перебираем ВСЕ колонки номенклатуры
    Для Каждого СтрКолонка Из ТаблицаКолонок Цикл
        Отбор.Номенклатура = СтрКолонка.Номенклатура;
        НайденныеСтроки = ТаблицаДанных.НайтиСтроки(Отбор);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            ОбластьСтрокаДанные.Параметры.Количество = НайденныеСтроки[0].Количество;
            ОбластьСтрокаДанные.Параметры.Сумма = НайденныеСтроки[0].Сумма;
        Иначе
            // Очищаем параметры для пустой ячейки
            ОбластьСтрокаДанные.Параметры.Количество = 0;
            ОбластьСтрокаДанные.Параметры.Сумма = 0;
        КонецЕсли;
        
        ТабДок.Присоединить(ОбластьСтрокаДанные);
    КонецЦикла;
КонецЦикла;

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

Многократный вызов метода ТабДок.Присоединить() в двух вложенных циклах создает большую нагрузку на интерфейс платформы, особенно если в отчете сотни позиций номенклатуры и тысячи контрагентов. Чтобы ускорить формирование отчета, воспользуемся следующими рекомендациями:

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

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

Если задача заключается именно в отказе от СКД, но нет жесткого требования выводить каждую ячейку вручную, рассмотрим встроенные инструменты платформы:

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