Разработка и Конфигурация · 12 мин
Как получить список дат в запросе 1С
Как получить список дат в запросе 1С: таблица значений, виртуальные таблицы регистра, соединение с границами периода. Примеры кода, ошибки и проверка результата.
Задача получить набор дат в запросе возникает постоянно: при построении отчётов по дням, при расчёте остатков на каждую дату, при проверке заполнения графиков и при выравнивании данных по календарю. Проблема в том, что язык запросов 1С не имеет встроенной функции генерации последовательности дат — в отличие от SQL с рекурсивными CTE или generate_series. Поэтому разработчику приходится использовать обходные приёмы, каждый из которых подходит для своих сценариев.
Основных подхода четыре: передать в запрос временную таблицу с датами, сформированную в коде; использовать виртуальную таблицу регистра накопления или сведений, где периодичность уже задана; построить даты арифметикой через соединение с эталонной таблицей чисел; либо задействовать регистр сведений с календарными данными, если он есть в конфигурации. Выбор зависит от того, есть ли в базе подходящие данные и какой диапазон дат нужен.
Ниже разобраны все рабочие способы с примерами кода, условиями выбора, типичными ошибками и способами проверить, что результат получен корректно.
Почему в запросе 1С нельзя просто «сгенерировать» даты
Язык запросов 1С — декларативный: он выбирает данные из существующих таблиц, но не создаёт строки «из ничего». Нет циклов, нет рекурсии, нет генераторов последовательностей. Любая строка в результате запроса должна происходить из какой-либо таблицы — физической, виртуальной или временной.
Из этого следует главный принцип: чтобы получить список дат, нужно либо найти таблицу, где эти даты уже есть, либо подложить такую таблицу в запрос извне. Все способы ниже — вариации этих двух идей.
Способ 1. Передать таблицу значений с датами через менеджер временных таблиц
Это самый гибкий и предсказуемый вариант. В коде создаётся таблица значений с колонкой типа «Дата», заполняется циклом по нужному диапазону и передаётся в запрос параметром. Дальше с ней можно работать как с обычной таблицей: соединять с документами, регистрами, группировать.
ТаблицаДат = Новый ТаблицаЗначений;
ТаблицаДат.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
ТекущаяДата = НачалоДня(ДатаНачала);
Пока ТекущаяДата <= ДатаОкончания Цикл
НоваяСтрока = ТаблицаДат.Добавить();
НоваяСтрока.Дата = ТекущаяДата;
ТекущаяДата = ТекущаяДата + 86400; // сутки в секундах
КонецЦикла;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаДат.Дата КАК Дата
|ПОМЕСТИТЬ ВТ_Даты
|ИЗ
| &ТаблицаДат КАК ТаблицаДат
|;
|ВЫБРАТЬ
| Даты.Дата КАК Дата
|ИЗ
| ВТ_Даты КАК Даты
|УПОРЯДОЧИТЬ ПО
| Даты.Дата";
Запрос.УстановитьПараметр("ТаблицаДат", ТаблицаДат);
Результат = Запрос.Выполнить().Выгрузить();
Обратите внимание на прибавление суток через 86400 секунд — это стандартный приём, поскольку функции добавления интервала в коде 1С работают только через ДобавитьМесяц, а для дней используется арифметика секунд. Внутри самого запроса для сдвига дат есть функция ДОБАВИТЬКДАТЕ, которая принимает тип интервала (ДЕНЬ, МЕСЯЦ и т.д.) и величину.
- 1Создайте таблицу значений с колонкой типа Дата
- 2Заполните её циклом от даты начала до даты окончания с шагом в сутки
- 3Создайте запрос с менеджером временных таблиц и поместите таблицу во ВТ
- 4Соедините ВТ с данными документов или регистров через ЛЕВОЕ СОЕДИНЕНИЕ
- 5Проверьте количество строк результата — оно должно совпадать с числом дней в периоде
Способ 2. Получить даты из виртуальной таблицы регистра
Если в конфигурации есть регистр накопления с периодичностью «День» (или регистр сведений с нужной периодичностью), его виртуальные таблицы уже содержат разрез по периодам. Например, таблица «Обороты» регистра накопления вернёт по строке на каждый день, в котором были движения.
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Обороты.Период КАК Дата
|ИЗ
| РегистрНакопления.Продажи.Обороты(
| &НачалоПериода, &КонецПериода, День, ) КАК Обороты
|УПОРЯДОЧИТЬ ПО
| Обороты.Период";
Ограничение очевидно: дней без движений в результате не будет. Если для отчёта нужны все дни периода, включая пустые, этот способ применяют как дополнение к способу 1 — таблицу дат соединяют левым соединением с оборотами, а отсутствующие значения обрабатывают через ЕСТЬNULL.
Для регистра сведений с периодичностью «в пределах секунды» или «в пределах дня» аналогично доступны виртуальные таблицы СрезПервых и СрезПоследних, но они возвращают срез на конкретную дату, а не список дат — для генерации календаря они не подходят.
Способ 3. Сгенерировать даты арифметикой через таблицу чисел
Если в базе есть любая таблица с достаточным количеством строк (например, справочник или регистр с гарантированно большим числом записей), из неё можно построить последовательность чисел, а числа превратить в смещения от базовой даты через ДОБАВИТЬКДАТЕ.
ВЫБРАТЬ
| ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, Числа.Номер - 1) КАК Дата
|ИЗ
| (ВЫБРАТЬ ПЕРВЫЕ 366
| ... поле с номером строки ...) КАК Числа
На практике этот приём громоздок: язык запросов 1С не имеет функции нумерации строк, поэтому номер приходится выводить через соединение таблицы самой с собой по условию «меньше или равно» с группировкой — так называемый трюк с накопительным счётчиком. Запрос получается тяжёлым и плохо читается, а по производительности обычно проигрывает передаче готовой таблицы значений.
Сравнение способов
Универсально, работает в любой конфигурации, полный контроль над шагом и диапазоном. Требует кода вокруг запроса.
Не требует кода, но возвращает только даты, где были движения. Пустые дни пропадают.
Работает без кода, но запрос сложный и медленный на больших диапазонах. Оправдан редко.
Если в конфигурации есть производственный календарь или графики — самый чистый источник дат, включая признак рабочего дня.
Способ 4. Использовать календарные регистры конфигурации
В типовых конфигурациях часто есть готовые источники дат: регистр сведений «ДанныеПроизводственногоКалендаря» (в БСП-based решениях), регистры графиков работы, периодические регистры сведений с праздниками. Если отчёт строится по рабочим дням, такой регистр — идеальный источник: он уже содержит и сами даты, и признак рабочего/выходного дня.
ВЫБРАТЬ
| Календарь.Дата КАК Дата
|ИЗ
| РегистрСведений.ДанныеПроизводственногоКалендаря КАК Календарь
|ГДЕ
| Календарь.Дата МЕЖДУ &Начало И &Конец
| И Календарь.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий)
Перед использованием проверьте, что календарь заполнен на весь нужный период — в типовых конфигурациях его заполняют заранее на ограниченный горизонт, и за его пределами запрос вернёт пустой результат без какой-либо ошибки.
Как соединить список дат с данными и не потерять пустые дни
Список дат редко нужен сам по себе — обычно к нему присоединяют обороты, остатки или документы. Чтобы дни без данных не исчезали, список дат должен стоять слева в ЛЕВОМ СОЕДИНЕНИИ, а NULL-значения из правой таблицы обрабатываются функцией ЕСТЬNULL.
ВЫБРАТЬ
| Даты.Дата КАК Дата,
| ЕСТЬNULL(Обороты.СуммаОборот, 0) КАК Сумма
|ИЗ
| ВТ_Даты КАК Даты
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(
| &Начало, &Конец, День, ) КАК Обороты
| ПО Даты.Дата = Обороты.Период
Критичный момент — совпадение границ периода. Параметры виртуальной таблицы оборотов должны покрывать тот же диапазон, что и таблица дат, иначе часть дат останется с нулями даже при наличии данных. Также следите за временной составляющей: если в таблице дат даты с началом дня (00:00:00), а в регистре период с временем, соединение по равенству не сработает — приводите обе стороны к началу дня через НАЧАЛОПЕРИОДА(Период, ДЕНЬ).
Производительность и ограничения
Передача таблицы значений в запрос материализуется во временную таблицу на сервере СУБД. Для диапазона в несколько лет по дням это тысячи строк — объём некритичный. Проблемы начинаются при передаче сотен тысяч строк или при соединении временной таблицы без индекса с большим регистром: план запроса может выбрать полное сканирование.
Практические рекомендации: помещайте таблицу дат во временную таблицу один раз и используйте её во всех последующих запросах пакета; после ПОМЕСТИТЬ добавьте ИНДЕКСИРОВАТЬ ПО Дата, если таблица участвует в соединениях; уничтожайте временные таблицы (УНИЧТОЖИТЬ) после использования в длинных пакетах. Для СКД таблицу дат удобно передавать как внешний набор данных через объектную схему или использовать периодичность компоновки, которая сама дополняет пропущенные периоды.
В СКД часто вообще не нужно генерировать даты вручную: настройка «Дополнение периода» у группировки по полю-периоду автоматически добавляет пропущенные дни, недели или месяцы в результат отчёта. Проверьте эту возможность до написания кода.
Как проверить результат
После выполнения запроса убедитесь, что список дат сформирован полностью и корректно. Базовые проверки: количество строк равно числу дней в диапазоне (для шага в сутки), первая строка равна дате начала, последняя — дате окончания, разрывов между соседними датами нет.
Для соединённых данных дополнительно сверьте итоговую сумму по отчёту с итогом по регистру за тот же период без разреза дат — суммы обязаны совпасть. Расхождение почти всегда означает потерю строк из-за неправильных границ периода или несовпадения времени в датах.
Как проверить результат
Равно числу дней между датой начала и окончания включительно
Строк меньше — потеряны даты, проверьте цикл заполнения или границы виртуальной таблицы
Все даты приведены к началу дня (00:00:00)
Часть дат со временем — соединение по равенству даст пропуски
Совпадает с итогом по регистру за весь период
Расхождение — данные отрезаны параметрами виртуальной таблицы или условием соединения
Присутствуют с нулевыми значениями (ЕСТЬNULL)
Отсутствуют или содержат NULL — замените соединение на левое и добавьте ЕСТЬNULL
Частые вопросы
Можно ли получить список месяцев, а не дней?
Да, все способы работают и для месяцев. В таблице значений заполняйте даты через ДобавитьМесяц в цикле, а в запросе приводите периоды к началу месяца функцией НАЧАЛОПЕРИОДА(Период, МЕСЯЦ). Для виртуальных таблиц регистров укажите периодичность «Месяц».
Как получить только рабочие дни?
Используйте регистр сведений производственного календаря, если он есть в конфигурации, и отфильтруйте по виду дня. Без такого регистра в чистом запросе определить рабочий день невозможно — праздники не вычисляются арифметикой.
Почему соединение по датам не находит совпадений?
Чаще всего из-за разного времени в датах: в одной таблице начало дня, в другой — момент документа с часами и минутами. Приведите обе стороны соединения через НАЧАЛОПЕРИОДА(Поле, ДЕНЬ).
Есть ли ограничение на размер передаваемой таблицы значений?
Жёсткого лимита нет, но при десятках тысяч строк растёт время материализации временной таблицы и риск неоптимального плана запроса. Для календарных списков дат такие объёмы практически не встречаются — год по дням это всего 366 строк.
Можно ли сгенерировать даты в запросе консоли СКД без кода?
Без внешнего набора данных — только через виртуальные таблицы регистров или приём с таблицей чисел. Но в большинстве отчётов проще включить дополнение периода в настройках группировки: компоновщик сам достроит пропущенные даты в выводе.
Что важно запомнить
Генератора дат в языке запросов 1С нет: либо передавайте готовую таблицу значений через менеджер временных таблиц, либо берите даты из виртуальных таблиц регистров и календарных регистров. Соединяйте список дат левым соединением, приводите даты к началу дня и не забывайте ЕСТЬNULL. Для отчётов на СКД сначала проверьте настройку дополнения периода — часто код вообще не нужен.