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