К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как получить список дат в запросе 1С

Как получить список дат в запросе 1С: таблица значений, виртуальные таблицы регистра, соединение с границами периода. Примеры кода, ошибки и проверка результата.

Задача получить набор дат в запросе возникает постоянно: при построении отчётов по дням, при расчёте остатков на каждую дату, при проверке заполнения графиков и при выравнивании данных по календарю. Проблема в том, что язык запросов 1С не имеет встроенной функции генерации последовательности дат — в отличие от SQL с рекурсивными CTE или generate_series. Поэтому разработчику приходится использовать обходные приёмы, каждый из которых подходит для своих сценариев.

Основных подхода четыре: передать в запрос временную таблицу с датами, сформированную в коде; использовать виртуальную таблицу регистра накопления или сведений, где периодичность уже задана; построить даты арифметикой через соединение с эталонной таблицей чисел; либо задействовать регистр сведений с календарными данными, если он есть в конфигурации. Выбор зависит от того, есть ли в базе подходящие данные и какой диапазон дат нужен.

Ниже разобраны все рабочие способы с примерами кода, условиями выбора, типичными ошибками и способами проверить, что результат получен корректно.

Почему в запросе 1С нельзя просто «сгенерировать» даты

Язык запросов 1С — декларативный: он выбирает данные из существующих таблиц, но не создаёт строки «из ничего». Нет циклов, нет рекурсии, нет генераторов последовательностей. Любая строка в результате запроса должна происходить из какой-либо таблицы — физической, виртуальной или временной.

Из этого следует главный принцип: чтобы получить список дат, нужно либо найти таблицу, где эти даты уже есть, либо подложить такую таблицу в запрос извне. Все способы ниже — вариации этих двух идей.

Способ 1. Передать таблицу значений с датами через менеджер временных таблиц

Это самый гибкий и предсказуемый вариант. В коде создаётся таблица значений с колонкой типа «Дата», заполняется циклом по нужному диапазону и передаётся в запрос параметром. Дальше с ней можно работать как с обычной таблицей: соединять с документами, регистрами, группировать.

ТаблицаДат = Новый ТаблицаЗначений;

ТаблицаДат.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));

ТекущаяДата = НачалоДня(ДатаНачала);

Пока ТекущаяДата <= ДатаОкончания Цикл

НоваяСтрока = ТаблицаДат.Добавить();

НоваяСтрока.Дата = ТекущаяДата;

ТекущаяДата = ТекущаяДата + 86400; // сутки в секундах

КонецЦикла;

Запрос = Новый Запрос;

Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

Запрос.Текст =

"ВЫБРАТЬ

| ТаблицаДат.Дата КАК Дата

|ПОМЕСТИТЬ ВТ_Даты

|ИЗ

| &ТаблицаДат КАК ТаблицаДат

|;

|ВЫБРАТЬ

| Даты.Дата КАК Дата

|ИЗ

| ВТ_Даты КАК Даты

|УПОРЯДОЧИТЬ ПО

| Даты.Дата";

Запрос.УстановитьПараметр("ТаблицаДат", ТаблицаДат);

Результат = Запрос.Выполнить().Выгрузить();

Обратите внимание на прибавление суток через 86400 секунд — это стандартный приём, поскольку функции добавления интервала в коде 1С работают только через ДобавитьМесяц, а для дней используется арифметика секунд. Внутри самого запроса для сдвига дат есть функция ДОБАВИТЬКДАТЕ, которая принимает тип интервала (ДЕНЬ, МЕСЯЦ и т.д.) и величину.

Порядок действий
  1. 1
    Создайте таблицу значений с колонкой типа Дата
  2. 2
    Заполните её циклом от даты начала до даты окончания с шагом в сутки
  3. 3
    Создайте запрос с менеджером временных таблиц и поместите таблицу во ВТ
  4. 4
    Соедините ВТ с данными документов или регистров через ЛЕВОЕ СОЕДИНЕНИЕ
  5. 5
    Проверьте количество строк результата — оно должно совпадать с числом дней в периоде

Способ 2. Получить даты из виртуальной таблицы регистра

Если в конфигурации есть регистр накопления с периодичностью «День» (или регистр сведений с нужной периодичностью), его виртуальные таблицы уже содержат разрез по периодам. Например, таблица «Обороты» регистра накопления вернёт по строке на каждый день, в котором были движения.

Запрос.Текст =

"ВЫБРАТЬ РАЗЛИЧНЫЕ

| Обороты.Период КАК Дата

|ИЗ

| РегистрНакопления.Продажи.Обороты(

| &НачалоПериода, &КонецПериода, День, ) КАК Обороты

|УПОРЯДОЧИТЬ ПО

| Обороты.Период";

Ограничение очевидно: дней без движений в результате не будет. Если для отчёта нужны все дни периода, включая пустые, этот способ применяют как дополнение к способу 1 — таблицу дат соединяют левым соединением с оборотами, а отсутствующие значения обрабатывают через ЕСТЬNULL.

Для регистра сведений с периодичностью «в пределах секунды» или «в пределах дня» аналогично доступны виртуальные таблицы СрезПервых и СрезПоследних, но они возвращают срез на конкретную дату, а не список дат — для генерации календаря они не подходят.

Способ 3. Сгенерировать даты арифметикой через таблицу чисел

Если в базе есть любая таблица с достаточным количеством строк (например, справочник или регистр с гарантированно большим числом записей), из неё можно построить последовательность чисел, а числа превратить в смещения от базовой даты через ДОБАВИТЬКДАТЕ.

ВЫБРАТЬ

| ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, Числа.Номер - 1) КАК Дата

|ИЗ

| (ВЫБРАТЬ ПЕРВЫЕ 366

| ... поле с номером строки ...) КАК Числа

На практике этот приём громоздок: язык запросов 1С не имеет функции нумерации строк, поэтому номер приходится выводить через соединение таблицы самой с собой по условию «меньше или равно» с группировкой — так называемый трюк с накопительным счётчиком. Запрос получается тяжёлым и плохо читается, а по производительности обычно проигрывает передаче готовой таблицы значений.

Сравнение способов

Таблица значений через ВТ

Универсально, работает в любой конфигурации, полный контроль над шагом и диапазоном. Требует кода вокруг запроса.

Виртуальная таблица регистра

Не требует кода, но возвращает только даты, где были движения. Пустые дни пропадают.

Арифметика через таблицу чисел

Работает без кода, но запрос сложный и медленный на больших диапазонах. Оправдан редко.

Регистр сведений с календарём

Если в конфигурации есть производственный календарь или графики — самый чистый источник дат, включая признак рабочего дня.

Способ 4. Использовать календарные регистры конфигурации

В типовых конфигурациях часто есть готовые источники дат: регистр сведений «ДанныеПроизводственногоКалендаря» (в БСП-based решениях), регистры графиков работы, периодические регистры сведений с праздниками. Если отчёт строится по рабочим дням, такой регистр — идеальный источник: он уже содержит и сами даты, и признак рабочего/выходного дня.

ВЫБРАТЬ

| Календарь.Дата КАК Дата

|ИЗ

| РегистрСведений.ДанныеПроизводственногоКалендаря КАК Календарь

|ГДЕ

| Календарь.Дата МЕЖДУ &Начало И &Конец

| И Календарь.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий)

Перед использованием проверьте, что календарь заполнен на весь нужный период — в типовых конфигурациях его заполняют заранее на ограниченный горизонт, и за его пределами запрос вернёт пустой результат без какой-либо ошибки.

Как соединить список дат с данными и не потерять пустые дни

Список дат редко нужен сам по себе — обычно к нему присоединяют обороты, остатки или документы. Чтобы дни без данных не исчезали, список дат должен стоять слева в ЛЕВОМ СОЕДИНЕНИИ, а NULL-значения из правой таблицы обрабатываются функцией ЕСТЬNULL.

ВЫБРАТЬ

| Даты.Дата КАК Дата,

| ЕСТЬNULL(Обороты.СуммаОборот, 0) КАК Сумма

|ИЗ

| ВТ_Даты КАК Даты

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(

| &Начало, &Конец, День, ) КАК Обороты

| ПО Даты.Дата = Обороты.Период

Критичный момент — совпадение границ периода. Параметры виртуальной таблицы оборотов должны покрывать тот же диапазон, что и таблица дат, иначе часть дат останется с нулями даже при наличии данных. Также следите за временной составляющей: если в таблице дат даты с началом дня (00:00:00), а в регистре период с временем, соединение по равенству не сработает — приводите обе стороны к началу дня через НАЧАЛОПЕРИОДА(Период, ДЕНЬ).

Типичные ошибки
01
Соединение даты без времени с периодом, содержащим время — строки не совпадают, результат пустой
02
Внутреннее соединение вместо левого — дни без движений пропадают из результата
03
Параметры виртуальной таблицы уже диапазона дат — ложные нули в конце периода
04
Забытый ЕСТЬNULL — в отчёте вместо нулей пустые значения, которые ломают итоги и диаграммы
05
Прибавление месяца как 30 дней вместо ДОБАВИТЬКДАТЕ(Дата, МЕСЯЦ, 1) — смещение границ на длинных периодах

Производительность и ограничения

Передача таблицы значений в запрос материализуется во временную таблицу на сервере СУБД. Для диапазона в несколько лет по дням это тысячи строк — объём некритичный. Проблемы начинаются при передаче сотен тысяч строк или при соединении временной таблицы без индекса с большим регистром: план запроса может выбрать полное сканирование.

Практические рекомендации: помещайте таблицу дат во временную таблицу один раз и используйте её во всех последующих запросах пакета; после ПОМЕСТИТЬ добавьте ИНДЕКСИРОВАТЬ ПО Дата, если таблица участвует в соединениях; уничтожайте временные таблицы (УНИЧТОЖИТЬ) после использования в длинных пакетах. Для СКД таблицу дат удобно передавать как внешний набор данных через объектную схему или использовать периодичность компоновки, которая сама дополняет пропущенные периоды.

В СКД часто вообще не нужно генерировать даты вручную: настройка «Дополнение периода» у группировки по полю-периоду автоматически добавляет пропущенные дни, недели или месяцы в результат отчёта. Проверьте эту возможность до написания кода.
ПЗ
Практическое замечание

Как проверить результат

После выполнения запроса убедитесь, что список дат сформирован полностью и корректно. Базовые проверки: количество строк равно числу дней в диапазоне (для шага в сутки), первая строка равна дате начала, последняя — дате окончания, разрывов между соседними датами нет.

Для соединённых данных дополнительно сверьте итоговую сумму по отчёту с итогом по регистру за тот же период без разреза дат — суммы обязаны совпасть. Расхождение почти всегда означает потерю строк из-за неправильных границ периода или несовпадения времени в датах.

Контрольные точки

Как проверить результат

Количество строк
Норма

Равно числу дней между датой начала и окончания включительно

Тревожный признак

Строк меньше — потеряны даты, проверьте цикл заполнения или границы виртуальной таблицы

Время в датах
Норма

Все даты приведены к началу дня (00:00:00)

Тревожный признак

Часть дат со временем — соединение по равенству даст пропуски

Итог по сумме
Норма

Совпадает с итогом по регистру за весь период

Тревожный признак

Расхождение — данные отрезаны параметрами виртуальной таблицы или условием соединения

Пустые дни
Норма

Присутствуют с нулевыми значениями (ЕСТЬNULL)

Тревожный признак

Отсутствуют или содержат NULL — замените соединение на левое и добавьте ЕСТЬNULL

Частые вопросы

Можно ли получить список месяцев, а не дней?

Да, все способы работают и для месяцев. В таблице значений заполняйте даты через ДобавитьМесяц в цикле, а в запросе приводите периоды к началу месяца функцией НАЧАЛОПЕРИОДА(Период, МЕСЯЦ). Для виртуальных таблиц регистров укажите периодичность «Месяц».

Как получить только рабочие дни?

Используйте регистр сведений производственного календаря, если он есть в конфигурации, и отфильтруйте по виду дня. Без такого регистра в чистом запросе определить рабочий день невозможно — праздники не вычисляются арифметикой.

Почему соединение по датам не находит совпадений?

Чаще всего из-за разного времени в датах: в одной таблице начало дня, в другой — момент документа с часами и минутами. Приведите обе стороны соединения через НАЧАЛОПЕРИОДА(Поле, ДЕНЬ).

Есть ли ограничение на размер передаваемой таблицы значений?

Жёсткого лимита нет, но при десятках тысяч строк растёт время материализации временной таблицы и риск неоптимального плана запроса. Для календарных списков дат такие объёмы практически не встречаются — год по дням это всего 366 строк.

Можно ли сгенерировать даты в запросе консоли СКД без кода?

Без внешнего набора данных — только через виртуальные таблицы регистров или приём с таблицей чисел. Но в большинстве отчётов проще включить дополнение периода в настройках группировки: компоновщик сам достроит пропущенные даты в выводе.

Итог

Что важно запомнить

Генератора дат в языке запросов 1С нет: либо передавайте готовую таблицу значений через менеджер временных таблиц, либо берите даты из виртуальных таблиц регистров и календарных регистров. Соединяйте список дат левым соединением, приводите даты к началу дня и не забывайте ЕСТЬNULL. Для отчётов на СКД сначала проверьте настройку дополнения периода — часто код вообще не нужен.