В практике разработки на платформе 1С часто возникает задача «расчесать» или детализировать заданный период (например, между двумя датами) на отдельные составляющие — чаще всего на месяцы. Это необходимо для построения отчетов, где нужно показать данные даже за те периоды, в которых не было фактических движений в регистрах. Разберем основные способы решения этой задачи: от стандартных механизмов запроса до хитростей с перемножением таблиц и настроек системы компоновки данных.
Это самый быстрый способ дополнения дат, предусмотренный языком запросов 1С. Рассмотрим ситуацию: нам нужно получить количество продаж, но при этом гарантировать, что в выборку попадут все месяцы интервала, даже если продаж в них не было. Проанализируем пример кода:
ВЫБРАТЬ
Продажи.Получатель КАК Получатель,
НАЧАЛОПЕРИОДА(Продажи.Дата, МЕСЯЦ) КАК Период,
КОЛИЧЕСТВО(Продажи.Ссылка) КАК КоличествоПокупок
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
Продажи.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
СГРУППИРОВАТЬ ПО
Получатель,
НАЧАЛОПЕРИОДА(Продажи.Дата, МЕСЯЦ)
ИТОГИ
СУММА(КоличествоПокупок)
ПО
Период ПЕРИОДАМИ(Месяц, &ДатаНачала, &ДатаОкончания)
Важная особенность: Как было отмечено в ходе обсуждения, ключевое слово ПЕРИОДАМИ работает исключительно при обходе результата запроса через объект ВыборкаИзРезультатаЗапроса. Если вы попытаетесь выгрузить результат такого запроса в ТаблицаЗначений методом Выгрузить(), пустые периоды в таблицу не попадут. Этот метод идеально подходит для вывода данных в иерархический список на форме или формирования печатной формы через обход выборки.
Если разработка ведется в типовой конфигурации (например, 1С:Зарплата и Управление Персоналом или 1С:ERP), мы можем воспользоваться готовыми данными системы. В таких конфигурациях есть регистр сведений ДанныеПроизводственногоКалендаря. Давайте выясним, как получить из него список месяцев:
Рассмотрим пример запроса:
ВЫБРАТЬ РАЗЛИЧНЫЕ
НАЧАЛОПЕРИОДА(Календарь.Дата, МЕСЯЦ) КАК МесяцПериода
ИЗ
РегистрСведений.ДанныеПроизводственногоКалендаря КАК Календарь
ГДЕ
Календарь.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
И Календарь.ПроизводственныйКалендарь = &КалендарьРФ
Полученную таблицу можно поместить во временную таблицу (ПОМЕСТИТЬ ВТ_Месяцы) и далее использовать ее для ЛЕВОЕ СОЕДИНЕНИЕ с вашими основными данными (например, с регистром накопления). Это обеспечит наличие строки для каждого месяца в итоговом результате.
Что делать, если в базе нет производственного календаря или данные в нем не заполнены? В этом случае мы можем применить математический подход — генерацию последовательности чисел, которую затем преобразуем в даты. Рассмотрим этот алгоритм по шагам:
1. Создадим временную таблицу из 10 цифр (от 0 до 9) с помощью объединения запросов. Это наш «базис».
2. Перемножим эту таблицу саму на себя (Cross Join) несколько раз, чтобы получить последовательность от 0 до 99, 999 или более.
3. Прибавим полученное число месяцев к начальной дате периода.
Проанализируем пример формирования такой таблицы:
ВЫБРАТЬ 0 КАК Цифра ПОМЕСТИТЬ Цифры ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
// ... и так далее до 9
;
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&ДатаНачала, МЕСЯЦ, т1.Цифра + т2.Цифра * 10 + т3.Цифра * 100) КАК ПериодМесяц
ПОМЕСТИТЬ ВТ_ВсеПериоды
ИЗ
Цифры КАК т1,
Цифры КАК т2,
Цифры КАК т3
ГДЕ
ДОБАВИТЬКДАТЕ(&ДатаНачала, МЕСЯЦ, т1.Цифра + т2.Цифра * 10 + т3.Цифра * 100) <= &ДатаОкончания
Этот метод гарантированно работает на любой платформе и конфигурации, так как не зависит от наличия данных в базе. Мы фактически «вычисляем» таблицу периодов «на лету».
Если задача стоит в рамках создания отчета на Системе компоновки данных (СКД), то «расчесывание» периодов можно делегировать самому механизму компоновки. Это зачастую проще и эффективнее, чем писать сложные запросы.
Для реализации дополнения выполним следующие действия:
ПериодМесяц).Месяц).Месяц.&НачалоПериода и &КонецПериода).В этом случае СКД сама добавит недостающие строки в группировку, даже если запрос вернул пустой результат. Это наиболее "красивый" способ с точки зрения платформенных механизмов при разработке отчетов.
Иногда проще сформировать список дат в модуле на языке 1С и передать его в запрос как параметр. Посмотрим, как это выглядит:
ТЗПериодов = Новый ТаблицаЗначений;
ТЗПериодов.Колонки.Добавить("Период", Новый ОписаниеТипов("Дата"));
ТекущаяДата = НачалоМесяца(Объект.ДатаНачала);
Пока ТекущаяДата <= Объект.ДатаОкончания Цикл
НоваяСтрока = ТЗПериодов.Добавить();
НоваяСтрока.Период = ТекущаяДата;
ТекущаяДата = ДобавитьМесяц(ТекущаяДата, 1);
КонецЦикла;
Запрос.УстановитьПараметр("ТЗПериодов", ТЗПериодов);
Затем в самом запросе мы используем конструкцию ВЫБРАТЬ * ИЗ &ТЗПериодов КАК ТЗПериодов. Это позволяет избежать сложной логики генерации дат внутри SQL-запроса и сделать код более читаемым для других разработчиков.
Подведем итог: выбор метода зависит от контекста. Для простых отчетов используйте ИТОГИ ПЕРИОДАМИ или механизмы СКД. Для сложных расчетных задач, где периоды нужны внутри временных таблиц запроса, лучше всего подойдет использование производственного календаря или универсальный метод с перемножением таблиц цифр.