При разработке отчетов на схеме компоновки данных (СКД) программисты часто сталкиваются с задачей динамического изменения шага периода. Например, пользователю необходимо получить данные отчета в разрезе дней, недель, месяцев, кварталов или лет, выбирая нужный шаг в качестве параметра. По умолчанию виртуальные таблицы регистров накопления требуют жесткого указания периодичности, что усложняет построение универсальных отчетов.
В этой статье мы подробно разберем несколько практических способов реализации динамического разбиения периода в СКД, проанализируем их преимущества и недостатки, а также рассмотрим решение сложной задачи построения произвольных интервалов (например, «за последние 5 лет и последние 6 кварталов»).
Это классический метод, который основывается на вычислении даты начала периода прямо в тексте запроса в зависимости от переданного параметра шага. Для реализации этого способа нам понадобится параметр &ШагПериода (например, с типом ссылка на перечисление или число) и поле Период, выбираемое из виртуальной таблицы регистра.
Важный нюанс: чтобы поле Период было доступно в запросе для виртуальных таблиц оборотов или остатков и оборотов, в параметрах самой виртуальной таблицы необходимо указать периодичность отличную от «Период» (например, Регистратор, Запись или День). Если оставить периодичность по умолчанию («Авто»), СКД может не предоставить базовое поле периода напрямую.
Рассмотрим пример построения такого запроса:
ВЫБРАТЬ
ВЫБОР &ШагПериода
КОГДА ЗНАЧЕНИЕ(Перечисление.Периодичность.День)
ТОГДА НАЧАЛОПЕРИОДА(ТоварыНаСкладах.Период, ДЕНЬ)
КОГДА ЗНАЧЕНИЕ(Перечисление.Периодичность.Неделя)
ТОГДА НАЧАЛОПЕРИОДА(ТоварыНаСкладах.Период, НЕДЕЛЯ)
КОГДА ЗНАЧЕНИЕ(Перечисление.Периодичность.Месяц)
ТОГДА НАЧАЛОПЕРИОДА(ТоварыНаСкладах.Период, МЕСЯЦ)
КОГДА ЗНАЧЕНИЕ(Перечисление.Периодичность.Квартал)
ТОГДА НАЧАЛОПЕРИОДА(ТоварыНаСкладах.Период, КВАРТАЛ)
КОГДА ЗНАЧЕНИЕ(Перечисление.Периодичность.Год)
ТОГДА НАЧАЛОПЕРИОДА(ТоварыНаСкладах.Период, ГОД)
ИНАЧЕ НАЧАЛОПЕРИОДА(ТоварыНаСкладах.Период, ДЕНЬ)
КОНЕЦ КАК Период,
ТоварыНаСкладах.Номенклатура КАК Номенклатура,
ТоварыНаСкладах.ВНаличииОборот КАК КоличествоОборот
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(&НачалоПериода, &КонецПериода, Регистратор, ) КАК ТоварыНаСкладах
При использовании этого метода мы получаем универсальное поле Период, которое можно использовать в группировках строк или колонок таблицы СКД. Однако у этого подхода есть существенный минус — производительность СУБД ухудшается на больших объемах данных из-за выполнения сложных условных вычислений над датами для каждой строки результата.
Более элегантным решением, использующим штатные механизмы СКД, является передача параметра периодичности непосредственно в параметры виртуальной таблицы регистра. Для этого используется специальный синтаксис фигурных скобок, которые понимает компоновщик макета СКД.
Рассмотрим структуру такого запроса:
ВЫБРАТЬ
Товары.Период КАК Период,
Товары.Номенклатура КАК Номенклатура,
Товары.ВНаличииОборот КАК Оборот
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(, , Месяц {(&Периодичность)}, , ) КАК Товары
В данном случае в качестве третьего параметра виртуальной таблицы указано значение по умолчанию (Месяц), а в фигурных скобках объявлено расширение {(&Периодичность)}. Параметр &Периодичность в настройках СКД обычно имеет тип Число. Для удобства пользователя мы можем задать доступные значения этого числового параметра со следующими представлениями:
Для того чтобы представление периода в отчете выводилось красиво и соответствовало выбранному шагу, настроим выражение представления для поля Период на вкладке «Наборы данных» в СКД:
ВЫБОР
КОГДА &Периодичность = 6 ТОГДА Формат(Период, "ДФ=dd.MM.yyyy")
КОГДА &Периодичность = 7 ТОГДА Формат(Период, "ДФ=dd.MM.yy") + " - " + Формат(КонецПериода(Период, "НЕДЕЛЯ"), "ДФ=dd.MM.yy")
КОГДА &Периодичность = 8 ТОГДА Формат(Период, "ДФ=dd.MM.yy") + " - " + Формат(КонецПериода(Период, "ДЕКАДА"), "ДФ=dd.MM.yy")
КОГДА &Периодичность = 9 ТОГДА Формат(Period, "ДФ='MMMM yyyy'")
КОГДА &Периодичность = 10 ТОГДА Формат(Период, "ДФ='q ""кв."" yyyy'")
КОГДА &Периодичность = 11 ТОГДА
ВЫБОР
КОГДА Период = НачалоПериода(Период, "ГОД") ТОГДА Формат(Период, "ДФ='""1 пг."" yyyy'")
ИНАЧЕ Формат(Период, "ДФ='""2 пг."" yyyy'")
КОНЕЦ
КОГДА &Периодичность = 12 ТОГДА Формат(Период, "ДФ='yyyy'")
ИНАЧЕ Формат(Период, "ДФ=dd.MM.yyyy")
КОНЕЦ
Этот способ работает значительно быстрее первого, так как СКД на уровне СУБД выбирает итоги регистра с уже рассчитанной периодичностью, минуя избыточные расчеты в полях выборки.
Еще один профессиональный подход заключается в отказе от ручного прописывания параметров периодичности в запросе. Вместо этого мы можем использовать штатную возможность автопериодичности СКД.
Разберем алгоритм реализации этого метода:
Авто.ПериодСекунда, ПериодДень, ПериодНеделя, ПериодМесяц, ПериодКвартал, ПериодГод.&Шаг со значениями "День", "Месяц", "Год").ПриКомпоновкеРезультата перехватываем управление и в зависимости от выбранного пользователем шага программно подменяем поле группировки в настройках компоновщика.Пример программного кода для подмены активного поля периода в группировке СКД:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Настройки по умолчанию или текущие настройки пользователя
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
// Получаем выбранный пользователем параметр шага
ПараметрШаг = Настройки.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ШагПериода"));
Если ПараметрШаг <> Неопределено И ПараметрШаг.Использование Тогда
Шаг = ПараметрШаг.Значение; // Например, "ПериодМесяц"
// Обходим структуру группировок структуры отчета и находим нужную группировку
Для Каждого ЭлементСтруктуры Из Настройки.Структура Цикл
Если ТипЗнч(ЭлементСтруктуры) = Тип("ГруппировкаКомпоновкиДанных") Тогда
// Проверяем, есть ли в полях группировки поле с именем "ПериодДень" (или другое базовое)
ПолеГруппировки = ЭлементСтруктуры.ПоляГруппировки.Элементы[0];
Если Строка(ПолеГруппировки.Поле) = "ПериодДень" Тогда
ПолеГруппировки.Поле = Новый ПолеКомпоновкиДанных(Шаг);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Выполняем компоновку макета с измененными настройками
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Этот способ считается наиболее чистым с точки зрения архитектуры, так как СКД сама оптимизирует запрос под ту группировку, которая реально используется в текущий момент вывода отчета.
Иногда заказчики выдвигают специфические требования к аналитичности отчетов. Например, требуется вывести данные за последние 5 лет крупными мазками (по годам), а за последний период (последние 6 кварталов) — детально. Стандартные последовательные группировки СКД здесь не помогут, поскольку эти периоды накладываются друг на друга.
Проанализируем оптимальные архитектурные решения такой задачи.
Самым гибким решением будет создание временной таблицы интервалов с последующим левым соединением с ней основной таблицы данных. Временную таблицу можно заполнить динамически программным путем или непосредственно в запросе.
Каждая строка в таблице интервалов должна содержать:
ДатаНачала — начало конкретного интервала.ДатаОкончания — окончание интервала.Представление — наименование колонки в отчете (например, "2021 год", "1 кв. 2025").Порядок — числовое поле для правильной сортировки колонок.Пример связи таблиц в запросе СКД:
ВЫБРАТЬ
Интервалы.Представление КАК ПериодПредставление,
Интервалы.Порядок КАК ПериодПорядок,
Продажи.Номенклатура КАК Номенклатура,
Продажи.СуммаОборот КАК Сумма
ИЗ
ВТ_Интервалы КАК Интервалы
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоАнализа, &КонецАнализа, Авто, ) КАК Продажи
ПО Продажи.Период МЕЖДУ Интервалы.ДатаНачала И Интервалы.ДатаОкончания
В настройках СКД мы делаем группировку колонок по полю ПериодПредставление, а в качестве сортировки указываем поле ПериодПорядок. Этот способ гарантирует вывод абсолютно любых, даже пересекающихся и неравномерных интервалов времени в единой плоской таблице.
Если нет жесткого требования выводить все данные в единой сетке колонок, мы можем решить задачу исключительно визуальными настройками СКД, создав две таблицы в структуре отчета:
Этот способ позволяет обойтись без сложного программирования и модификации запросов, однако он формирует две раздельные визуальные области в итоговом документе.
При работе с периодами в СКД хорошим тоном считается использование стандартного предопределенного типа СтандартныйПериод для удобства пользователя. Вместо двух параметров &НачалоПериода и &КонецПериода, заставляющих пользователя вводить даты вручную, создайте один параметр &ПериодОтчета с типом СтандартныйПериод. Для исходных параметров начала и конца периода установите флаг «Ограничение доступности» и пропишите в их выражениях:
&НачалоПериода: &ПериодОтчета.ДатаНачала&КонецПериода: &ПериодОтчета.ДатаОкончанияПри таком подходе СКД сама автоматически приведет время начала к 00:00:00, а конца — к 23:59:59 выбранного диапазона дат, избавив пользователя от ошибок ручного ввода.