Как рационально сгруппировать расчетные данные и вывести их в печатную форму в 1С?

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

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

В этой статье мы подробно разберем, какие архитектурные подходы и объекты встроенного языка 1С наиболее рационально использовать для решения этой задачи. Мы проанализируем различные варианты реализации — от классических запросов до использования Системы компоновки данных (СКД) и механизмов автогруппировки строк табличного документа.

Вариант 1. Перенос расчетов и группировки в запрос (Оптимальный подход)

Перед тем как приступать к программной обработке данных в коде, всегда следует задать себе вопрос: можно ли перенести эти вычисления на уровень СУБД? Выполнение расчетов в цикле обхода выборки считается классическим антипаттерном производительности в экосистеме 1С. Группировка данных средствами SQL-сервера происходит значительно быстрее, чем обработка аналогичного объема данных в оперативной памяти сервера приложений 1С.

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

  1. Временные таблицы: поместите промежуточные данные во временную таблицу в пакете запросов. На этом этапе можно применить конструкции ВЫБОР КОГДА ... ТОГДА для выполнения логических расчетов.
  2. Группировка по дате: чтобы избавиться от детального времени и сгруппировать данные по дням, неделям или месяцам, используйте функцию НАЧАЛОПЕРИОДА(Период, ДЕНЬ) в секции СГРУППИРОВАТЬ ПО.

Рассмотрим пример запроса, который сразу возвращает сгруппированные и рассчитанные показатели:


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

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

Вариант 2. Использование СКД с внешним набором данных (Таблица значений)

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

Многие разработчики ошибочно полагают, что СКД работает только с прямыми запросами к базе данных. На самом деле СКД великолепно справляется с выводом обычных таблиц значений через механизм Внешнего набора данных.

Выясним, как пошагово реализовать эту схему:

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

Посмотрим на пример программного вызова СКД с передачей внешней таблицы значений:


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

// (Здесь выполняется ваш алгоритм заполнения и расчета данных)
// ...

// 2. Инициализируем внешние наборы данных
ВнешниеНаборы = Новый Структура;
ВнешниеНаборы.Вставить("ТаблицаРезультатов", ТаблицаДляПечати);

// 3. Получаем схему компоновки данных из макета объекта (например, обработки или отчета)
СхемаКомпоновки = ПолучитьМакет("МакетСКД");
Настройки = СхемаКомпоновки.НастройкиПоУмолчанию;

// 4. Запускаем стандартный цикл компоновки результата
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновки, Настройки);

ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборы);

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

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

Преимущества этого метода: СКД автоматически возьмет на себя всю работу по группировке данных, сортировке, расчету итогов и оформлению внешнего вида табличного документа. Вам не придется вручную описывать области макета и организовывать сложные вложенные циклы.

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

Если вы по каким-то причинам не хотите использовать СКД, а предпочитаете классический вывод через макет табличного документа, вам необходимо предварительно подготовить и сгруппировать данные в коде. Самым удобным контейнером для этого является объект ТаблицаЗначений.

Для быстрой группировки и суммирования показателей используйте встроенный метод Свернуть(). Он работает на уровне платформы (написан на C++), благодаря чему выполняется практически мгновенно даже на больших объемах данных.

Проанализируем пример использования метода свертки:


// Предположим, у нас есть заполненная таблица "ТаблицаРасчетов"
// Колонки таблицы: Дата, Менеджер, Сумма, Количество

// Сворачиваем таблицу по ключевым полям, суммируя ресурсы
ТаблицаРасчетов.Свернуть("Дата, Менеджер", "Сумма, Количество");

// Сортируем полученный результат по дате для корректного вывода
ТаблицаРасчетов.Сортировать("Дата Возр");

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

Если вам требуется построить многоуровневую иерархическую структуру (например, Дата -> Менеджер -> Детальные строки), то вместо плоской таблицы значений лучше использовать объект ДеревоЗначений. Он позволяет создавать неограниченное количество уровней вложенности строк, которые затем можно рекурсивно обойти и вывести в табличный документ.

Вариант 4. Использование автогруппировки строк в Табличном документе

Когда стоит задача не просто сгруппировать данные визуально, но и предоставить пользователю интерактивную возможность сворачивать и разворачивать строки (появление символов «плюс» и «минус» на полях печатной формы), на помощь приходит встроенный механизм автогруппировки строк табличного документа.

Рассмотрим, как правильно использовать методы НачатьАвтогруппировкуСтрок() и ЗакончитьАвтогруппировкуСтрок(). Главный секрет кроется в правильном указании уровней группировки при выводе областей макета.

Разберем детальный пример реализации:


ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("МакетПечати");

// Включаем режим автоматической группировки строк
ТабДок.НачатьАвтогруппировкуСтрок();

// Выводим шапку документа
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ТабДок.Вывести(ОбластьШапка);

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

// Выключаем режим группировки
ТабДок.ЗакончитьАвтогруппировкуСтрок();

// Задаем отображение кнопок группировки на панели
ТабДок.ПоказатьСетку = Ложь;
ТабДок.ОтображатьСетку = Ложь;
ТабДок.ОтображатьЗаголовки = Ложь;

// Показываем структуру в свернутом до 1-го уровня виде
ТабДок.ПоказатьУровеньГруппировокСтрок(1);

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

Важное примечание: при ручном выводе большого объема данных (более 5 000 - 10 000 строк) последовательный вывод областей через метод Вывести() с включенной автогруппировкой может работать медленно. В таких случаях СКД отработает значительно быстрее, так как генерация макета в СКД оптимизирована на уровне ядра платформы 1С.

Резюме и рекомендации

Выбор конкретного технического решения зависит от сложности вашей задачи и объема обрабатываемых данных. Подведем итоги нашего анализа:

← На главную