Даты в языке запросов 1С: литералы, параметры, периоды и фильтры
Содержание
Дату в запросе 1С указывают двумя способами. Константой — через ДАТАВРЕМЯ(2024, 1, 15). Или параметром — &ДатаНачала, значение которого передают из кода методом Запрос.УстановитьПараметр(). В рабочем коде почти всегда нужен второй вариант: он гибче и не ломается при смене периода.
Самая частая беда с датами в запросе — потерянный последний день периода. Конец периода приходит как 00:00:00, и документы за 31-е число не попадают в отчёт. Лечится это одной строкой, и про неё ниже по тексту.
Дата константой в тексте запроса: функция ДАТАВРЕМЯ
В языке запросов нет строкового литерала даты. Написать '15.01.2024' или "20240115" и сравнить с полем Дата не получится. Для константы есть функция ДАТАВРЕМЯ (англ. DATETIME).
Синтаксис:
ДАТАВРЕМЯ(Год, Месяц, День)
ДАТАВРЕМЯ(Год, Месяц, День, Час, Минута, Секунда)
Пример — документы реализации за 15 января 2024 года:
ВЫБРАТЬ
Реализация.Ссылка,
Реализация.Дата
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Дата МЕЖДУ ДАТАВРЕМЯ(2024, 1, 15) И ДАТАВРЕМЯ(2024, 1, 15, 23, 59, 59)
Что важно знать про ДАТАВРЕМЯ:
- 🔢 Аргументы — только числовые константы. Переменную, параметр или поле туда подставить нельзя: запрос не пройдёт синтаксический контроль.
- 🕛 Если время не указано, берётся 00:00:00.
ДАТАВРЕМЯ(2024, 1, 31)— это начало суток 31 января, а не весь день. - ⬜ Пустая дата записывается как
ДАТАВРЕМЯ(1, 1, 1). Так проверяют незаполненные реквизиты.
Пример отбора документов с незаполненной датой оплаты:
ГДЕ
Счет.ДатаОплаты = ДАТАВРЕМЯ(1, 1, 1)
Константу оправдано писать в разовом запросе в консоли или в условии, которое никогда не меняется. Например, «дата начала учёта в базе». Во всех остальных случаях — параметр.
Параметры запроса: как передать дату из кода
Параметр в тексте запроса начинается с амперсанда: &НачалоПериода. Значение задают до выполнения запроса. Это основной способ указать дату в запросе 1С для отчётов, обработок и печатных форм.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка,
| Реализация.Дата,
| Реализация.СуммаДокумента
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаНач));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаКон));
Выборка = Запрос.Выполнить().Выбрать();
Почему именно так:
- ✅
НачалоДня()иКонецДня()срезают время на стороне встроенного языка. Если пользователь выбрал даты в поле ввода без времени,ДатаКонпридёт как 00:00:00. БезКонецДня()документы последнего дня выпадут из выборки. - ✅ Один текст запроса работает на любом периоде. Его не нужно пересобирать склейкой строк.
- ✅ Платформа сама передаёт значение в СУБД в нужном формате. Нет риска ошибиться с форматом даты конкретного сервера.
Не подставляйте дату в текст запроса через конкатенацию строк вроде "... ГДЕ Дата > " + Строка(Дата). Строковое представление даты зависит от локали и в запросе не распознаётся как дата.
Период из поля «Стандартный период»
Если на форме реквизит типа СтандартныйПериод, параметры берут из его свойств:
Запрос.УстановитьПараметр("НачалоПериода", Период.ДатаНачала);
Запрос.УстановитьПараметр("КонецПериода", Период.ДатаОкончания);
ДатаОкончания у стандартного периода уже приходит концом дня, дополнительно оборачивать её в КонецДня() не нужно, но и вреда не будет.
Дата в запросе СКД
В схеме компоновки данных параметр пишут так же — &НачалоПериода. Его значение пользователь задаёт в настройках отчёта. Частый приём: завести в СКД параметр Период типа СтандартныйПериод, а у параметров НачалоПериода и КонецПериода указать выражения &Период.ДатаНачала и &Период.ДатаОкончания и снять им флаг доступности для пользователя.
Функции языка запросов для работы с датами
Внутри запроса дату можно разобрать на части, округлить до периода и сдвинуть. Основные функции:
| Функция | Что делает | Пример |
|---|---|---|
НАЧАЛОПЕРИОДА(Дата, Период) | Начало минуты, часа, дня, недели, месяца, квартала, года, декады, полугодия | НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ) |
КОНЕЦПЕРИОДА(Дата, Период) | Последняя секунда того же периода | КОНЕЦПЕРИОДА(&Дата, ДЕНЬ) |
ДОБАВИТЬКДАТЕ(Дата, Период, Число) | Сдвиг даты на N периодов, N может быть отрицательным | ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -30) |
РАЗНОСТЬДАТ(Дата1, Дата2, Период) | Разница между датами в секундах, минутах, часах, днях, месяцах, кварталах или годах | РАЗНОСТЬДАТ(Дата, &ТекДата, ДЕНЬ) |
ГОД(), КВАРТАЛ(), МЕСЯЦ() | Номер года, квартала, месяца | МЕСЯЦ(Дата) = 12 |
ДЕНЬ(), ДЕНЬГОДА() | День месяца и порядковый день в году | ДЕНЬ(Дата) |
НЕДЕЛЯ(), ДЕНЬНЕДЕЛИ() | Номер недели в году и день недели (1 — понедельник, 7 — воскресенье) | ДЕНЬНЕДЕЛИ(Дата) В (6, 7) |
ЧАС(), МИНУТА(), СЕКУНДА() | Части времени | ЧАС(Дата) >= 18 |
Группировка продаж по месяцам
Классическая задача — свернуть документы по месяцам. Дату округляют до начала месяца и группируют по результату:
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ) КАК Месяц,
СУММА(Реализация.СуммаДокумента) КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И Реализация.Проведен
СГРУППИРОВАТЬ ПО
НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ)
УПОРЯДОЧИТЬ ПО
Месяц
Поле Месяц в результате — это дата 01.ММ.ГГГГ 00:00:00. Её удобно выводить с форматом ДФ='ММММ гггг' и сортировать без лишних преобразований.
Месяцы без продаж: ИТОГИ ПО ПЕРИОДАМИ
Обычная группировка не покажет месяц, в котором не было ни одного документа. Если в отчёте нужна сплошная шкала, используют ИТОГИ ... ПО ... ПЕРИОДАМИ:
ИТОГИ
СУММА(Сумма)
ПО
Месяц ПЕРИОДАМИ(МЕСЯЦ, &НачалоПериода, &КонецПериода)
Пустые периоды появятся при обходе результата с типом обхода ОбходРезультатаЗапроса.ПоГруппировкам и выборке с параметром "ВСЕ" для группировки по периоду. Этот приём часто забывают, а потом дорисовывают пропуски в коде.
Отбор за период и сравнение дат
Для сравнения дат в условии ГДЕ подходят обычные операторы: =, <>, <, >, <=, >= и конструкция МЕЖДУ ... И ....
| Задача | Условие |
|---|---|
| Документы за период включительно | Дата МЕЖДУ &Начало И &Конец |
| То же, без проблем с концом дня | Дата >= &Начало И Дата < &НачалоСледующегоДня |
| Строго после даты | Дата > &Дата |
| Срок оплаты прошёл | ДатаОплаты < &ТекущаяДата И ДатаОплаты <> ДАТАВРЕМЯ(1, 1, 1) |
| Документы одного дня | НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) = &День (медленно, см. ниже) |
МЕЖДУ включает обе границы. Поэтому МЕЖДУ &Начало И &Конец с &Конец = 31.01.2024 00:00:00 отберёт документы до полуночи 31 января и пропустит весь день 31-го.
Надёжный вариант — полуоткрытый интервал. Нижняя граница включается, верхняя нет:
ГДЕ
Реализация.Дата >= &НачалоПериода
И Реализация.Дата < &НачалоСледующегоПериода
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Дата));
Запрос.УстановитьПараметр("НачалоСледующегоПериода", ДобавитьМесяц(НачалоМесяца(Дата), 1));
Такое условие не зависит от того, есть ли у даты время, и не теряет документы, проведённые в 23:59:59.
Сравнение двух полей-дат
Даты разных полей сравнивают так же, как числа. Пример — заказы, отгруженные позже обещанного срока, с числом дней задержки:
ВЫБРАТЬ
Заказ.Ссылка,
РАЗНОСТЬДАТ(Заказ.ДатаОтгрузки, Реализация.Дата, ДЕНЬ) КАК ДнейЗадержки
ИЗ
Документ.ЗаказКлиента КАК Заказ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
ПО Реализация.ЗаказКлиента = Заказ.Ссылка
ГДЕ
Реализация.Дата > КОНЕЦПЕРИОДА(Заказ.ДатаОтгрузки, ДЕНЬ)
Имена объектов и реквизитов здесь условные: в вашей конфигурации документ заказа и реквизит связи могут называться иначе. Смысл приёма — КОНЕЦПЕРИОДА превращает дату обещания в конец дня, чтобы отгрузка в тот же день вечером не считалась просрочкой.
РАЗНОСТЬДАТ(Д1, Д2, ДЕНЬ) возвращает Д2 − Д1. Если перепутать порядок аргументов, задержки станут отрицательными.
Плавающий период: «последние 30 дней», «текущий месяц», «прошлый квартал»
Скользящие периоды задают двумя путями. Либо вычисляют границы в коде и передают параметрами. Либо передают одну опорную дату, а границы считают в самом запросе.
Опорную дату почти всегда передают параметром. Функции «текущая дата» в языке запросов нет, а брать её в коде лучше через ТекущаяДатаСеанса(): она учитывает часовой пояс сеанса. ТекущаяДата() возвращает время компьютера, на котором выполняется код, — в клиент-серверном варианте это время сервера 1С.
Примеры границ, вычисляемых в запросе от параметра &ТекДата:
| Период | Начало | Конец |
|---|---|---|
| Последние 30 дней | ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&ТекДата, ДЕНЬ), ДЕНЬ, -29) | КОНЕЦПЕРИОДА(&ТекДата, ДЕНЬ) |
| Текущий месяц | НАЧАЛОПЕРИОДА(&ТекДата, МЕСЯЦ) | КОНЕЦПЕРИОДА(&ТекДата, МЕСЯЦ) |
| Прошлый месяц | НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&ТекДата, МЕСЯЦ, -1), МЕСЯЦ) | КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&ТекДата, МЕСЯЦ, -1), МЕСЯЦ) |
| Прошлый квартал | НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&ТекДата, КВАРТАЛ, -1), КВАРТАЛ) | КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&ТекДата, КВАРТАЛ, -1), КВАРТАЛ) |
| С начала года | НАЧАЛОПЕРИОДА(&ТекДата, ГОД) | КОНЕЦПЕРИОДА(&ТекДата, ДЕНЬ) |
Проверим первую строку. Если &ТекДата — 15 марта, начало периода — 15 марта минус 29 дней, то есть 15 февраля в високосном году и 14 февраля в обычном. Вместе с сегодняшним днём это ровно 30 календарных дней.
Пример — контрагенты без отгрузок 90 дней и дольше:
ВЫБРАТЬ
Реализация.Контрагент,
МАКСИМУМ(Реализация.Дата) КАК ПоследняяОтгрузка
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
ИМЕЮЩИЕ
МАКСИМУМ(Реализация.Дата) < ДОБАВИТЬКДАТЕ(&ТекДата, ДЕНЬ, -90)
Если границы периода нужны и в запросе, и в заголовке отчёта, считайте их в коде. Тогда «Продажи за март» в шапке и цифры в таблице гарантированно относятся к одному периоду.
Строка в дату и дата в строку
Прямого преобразования строки в дату в языке запросов нет. ВЫРАЗИТЬ(Поле КАК ДАТА) строку "15.01.2024" в дату не превратит. Поэтому строку переводят в дату до запроса, во встроенном языке.
Варианты во встроенном языке:
- 📅 Формат
ГГГГММДДилиГГГГММДДччммссконструктор понимает напрямую:Дата("20240115")илиДата("20240115143000"). - 📅 Строку вида
15.01.2024разбирают на части и собирают черезДата(Год, Месяц, День):
Части = СтрРазделить("15.01.2024", ".");
ДатаЗначение = Дата(Число(Части[2]), Число(Части[1]), Число(Части[0]));
Запрос.УстановитьПараметр("Дата", ДатаЗначение);
- 📅 Дату из XML и JSON в формате
2024-01-15T00:00:00читают черезXMLЗначение(Тип("Дата"), Строка).
Обратное преобразование — дату в строку — тоже удобнее делать после выполнения запроса:
Формат(Выборка.Дата, "ДФ=dd.MM.yyyy")
В отчёте на СКД формат задают в оформлении поля, в табличном документе — в свойствах ячейки или через Формат(). Внутри запроса части даты можно получить числами (ГОД(), МЕСЯЦ(), ДЕНЬ()), но склеивать из них строку ради вывода смысла нет.
Если в запросе нужно сравнить с датой, хранящейся в строковом реквизите, — это повод исправить структуру данных. Хранить дату в строке и разбирать её в каждом запросе медленно и ненадёжно.
Как не замедлить запрос условием на дату
Условие на дату — самое частое в отчётах, и от его формы зависит скорость. У таблиц документов платформа сама строит индекс по полю Дата. Регистры индексируются по Период. Индекс используется, только если поле сравнивается «как есть».
| Медленно | Быстро |
|---|---|
ГОД(Дата) = 2024 | Дата >= &НачалоГода И Дата < &НачалоСледующегоГода |
НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) = &День | Дата МЕЖДУ НАЧАЛОПЕРИОДА(&День, ДЕНЬ) И КОНЕЦПЕРИОДА(&День, ДЕНЬ) |
МЕСЯЦ(Дата) = 3 И ГОД(Дата) = 2024 | Дата МЕЖДУ &НачалоМарта И &КонецМарта |
Условие ГДЕ Период МЕЖДУ ... к виртуальной таблице оборотов | Период в параметрах виртуальной таблицы |
Функция над полем заставляет СУБД вычислить её для каждой строки — индекс не помогает. Функция над параметром вычисляется один раз, поэтому правый столбец быстрый.
Виртуальные таблицы регистров
Для регистров период передают в параметры виртуальной таблицы, а не в ГДЕ:
ВЫБРАТЬ
Продажи.Номенклатура,
Продажи.СуммаОборот
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК Продажи
Так платформа сразу берёт данные из таблицы итогов за нужный период. Условие в ГДЕ применится уже к готовому результату, и СУБД перелопатит лишнее.
Остатки на дату: граница
С остатками есть ловушка. Остатки(&Дата) возвращает остаток на начало указанного момента. Если &Дата = 31.01.2024 00:00:00, движения за 31 января не попадут.
Два правильных варианта:
// остаток на конец дня
Запрос.УстановитьПараметр("Дата", КонецДня(Дата));
// или граница, включающая сам момент
Запрос.УстановитьПараметр("Дата", Новый Граница(КонецДня(Дата), ВидГраницы.Включая));
Когда нужен остаток именно на момент документа — например, при проведении — передают Новый Граница(Документ.МоментВремени(), ВидГраницы.Исключая). Тогда учитываются все движения до документа, включая другие документы того же дня и секунды, но без движений самого документа.
Проверить план выполнения можно средствами СУБД. В MS SQL — через SQL Server Profiler или фактический план в Management Studio, в PostgreSQL — через EXPLAIN ANALYZE для запроса, пойманного в технологическом журнале. Если в плане по таблице документов сканирование всей таблицы, а в запросе функция над датой, — причина найдена.
Ошибки с датами, которые чаще всего ломают выборку
КонецДня(ДатаКон) или используйте условие Дата < &НачалоСледующегоДня.Остатки(&Дата) ушла дата без времени, и движения этого дня не учтены. Передайте КонецДня() или Новый Граница(..., ВидГраницы.Включая).ДАТАВРЕМЯ принимает только числа. Для переменных используйте параметр &Дата.NULL или с пустой строкой. Пустая дата в запросе — ДАТАВРЕМЯ(1, 1, 1); для полей из левого соединения добавьте ЕСТЬ NULL.ГОД(Дата) = &Год — функция над индексированным полем. Перепишите на интервал между началом года и началом следующего.ТекущаяДата(). Передавайте в запрос ТекущаяДатаСеанса().Ещё одна тонкость — тип параметра. Если передать в &Дата значение Неопределено (например, поле на форме не заполнено), условие Дата >= &Дата не отберёт ничего, и пользователь увидит пустой отчёт без ошибки. Перед выполнением проверяйте, что дата заполнена, и выводите понятное сообщение.
Частые вопросы
Можно ли в запросе 1С указать дату строкой, например '01.01.2024'?
ДАТАВРЕМЯ(2024, 1, 1) или параметр со значением типа Дата.Как в запросе отобрать документы только за сегодня?
&Сегодня = ТекущаяДатаСеанса() и поставьте условие Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Сегодня, ДЕНЬ) И КОНЕЦПЕРИОДА(&Сегодня, ДЕНЬ).Как отрезать время у даты прямо в запросе?
НАЧАЛОПЕРИОДА(Дата, ДЕНЬ). Результат — та же дата с временем 00:00:00. В условии ГДЕ по индексированному полю так лучше не делать, а для группировки и вывода — нормально.Чем отличаются МЕЖДУ и пара условий >= и <?
МЕЖДУ включает обе границы, поэтому конец периода нужно передавать с временем 23:59:59. Пара >= и < с началом следующего дня не зависит от времени и надёжнее при любых данных.Как посчитать возраст или стаж в годах в запросе?
РАЗНОСТЬДАТ(ДатаРождения, &НаДату, ГОД) вернёт разницу в годах. Проверьте на своих данных, как функция округляет неполный год, прежде чем использовать результат в расчётах.