Часто при разработке отчетов или алгоритмов в 1С возникает задача получить непрерывную последовательность дат (каждый день, месяц или год) за определенный период. Если просто выбирать даты из журналов документов или регистров, мы неизбежно столкнемся с пропусками — например, в выходные дни документов может не быть. Использование встроенного регламентированного производственного календаря не всегда возможно или целесообразно. Давайте подробно разберем несколько эффективных способов решения этой задачи.
Платформа 1С умеет самостоятельно дополнять выборку недостающими датами, если мы используем конструкцию ИТОГИ ПО ... ПЕРИОДАМИ и правильно настроим обход результата. Рассмотрим функцию, которая генерирует таблицу значений с нужным интервалом дат:
Функция ПолучитьТаблицуДатЗаПериод(НачДата, КонДата, Интервал)
тзИнтервалов = Новый ТаблицаЗначений;
тзИнтервалов.Колонки.Добавить("НачИнтервала", Новый ОписаниеТипов("Дата"));
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| НАЧАЛОПЕРИОДА(&НачДата, День) КАК НачИнтервала
|УПОРЯДОЧИТЬ ПО
| НачИнтервала
|ИТОГИ ПО
| НачИнтервала ПЕРИОДАМИ(" + Интервал + ", &НачДата, &КонДата)";
Запрос.УстановитьПараметр("НачДата", НачДата);
Запрос.УстановитьПараметр("КонДата", КонДата);
// Третий параметр "ВСЕ" заставляет платформу дополнить пустые периоды
Выборка = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "НачИнтервала", "ВСЕ");
Пока Выборка.Следующий() Цикл
НоваяСтрока = тзИнтервалов.Добавить();
НоваяСтрока.НачИнтервала = Выборка.НачИнтервала;
КонецЦикла;
Возврат тзИнтервалов;
КонецФункции
Важный момент здесь заключается в использовании третьего параметра "ВСЕ" в методе Выбрать(). Благодаря ему на уровне сервера приложений платформа генерирует все даты в заданном диапазоне. Однако стоит помнить, что использовать ИТОГИ ПО при помещении результата во временную таблицу напрямую нельзя.
Если нам необходимо получить даты внутри большого пакетного запроса, мы можем сгенерировать таблицу значений с датами программно и передать её параметром в запрос. Проанализируем этот подход:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ_Дни.День КАК День
|ПОМЕСТИТЬ ВТ_Дни
|ИЗ
| &ТЗ_Дни КАК ТЗ_Дни
|;
|ВЫБРАТЬ * ИЗ ВТ_Дни"; // Дальнейшая работа с датами
ТекДата = НачалоМесяца(ТекущаяДата());
КонДата = ТекущаяДата();
ТЗ = Новый ТаблицаЗначений;
КвалификаторыДаты = Новый КвалификаторыДаты(ЧастиДаты.ДатаВремя);
ОписаниеДаты = Новый ОписаниеТипов("Дата", , ,КвалификаторыДаты);
ТЗ.Колонки.Добавить("День", ОписаниеДаты);
// Заполняем таблицу нужными датами
Пока НачалоДня(ТекДата) <= КонДата Цикл
Стр = ТЗ.Добавить();
Стр.День = ТекДата;
ТекДата = КонецДня(ТекДата) + 1;
КонецЦикла;
Запрос.УстановитьПараметр("ТЗ_Дни", ТЗ);
Этот код простой, понятный и легко читаемый. Программная предварительная обработка позволяет гибко настроить логику перед выполнением тяжелых запросов.
Что делать, если у нас нет возможности писать код на встроенном языке (например, при разработке динамического списка или строго внутри СКД)? Выясним, как сгенерировать даты математическим путем прямо в тексте запроса.
Для этого используется декартово произведение маленьких таблиц с цифрами от 0 до 9. Соединяя их друг с другом, мы получаем числа 0-99, 0-999 и так далее. Затем эти числа прибавляются к дате начала. Посмотрим на пример:
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, aa.a * 1000 + bb.b * 100 + cc.c * 10 + dd.d) КАК Период
ИЗ
(ВЫБРАТЬ 0 КАК a ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3
ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7
ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК aa
ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК b ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК bb ПО (ИСТИНА)
ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК c ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК cc ПО (ИСТИНА)
ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК d ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК dd ПО (ИСТИНА)
ГДЕ
aa.a * 1000 + bb.b * 100 + cc.c * 10 + dd.d <= РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ДЕНЬ)
Такой запрос объединяет таблицу саму с собой четыре раза, что позволяет сгенерировать 10 000 дней (около 27 лет). Функция ДОБАВИТЬКДАТЕ преобразует полученный числовой индекс в реальную дату. Конструкция работает быстро и не требует наличия физических таблиц в базе.
Если ваша задача — просто вывести отчет без «дырок» в датах, то строить сложные запросы вообще не нужно. Разберем по шагам, как это решается в Системе Компоновки Данных:
ПериодДень).СКД автоматически добавит недостающие даты при выводе результата, даже если по ним не было оборотов или остатков.
Подведем итог: выбор метода зависит от конкретной архитектуры вашего решения. Для отчетов лучше всего подходит штатное дополнение периодов в СКД. Для программной обработки — использование переданной таблицы значений или выборки с обходом по группировкам. А для сугубо запросной логики выручит математическая генерация через объединение цифр.