Дата в запросе 1С задаётся литералом ДАТАВРЕМЯ или параметром, а для работы с ней в языке запросов есть полтора десятка функций — от ГОД() и ДЕНЬНЕДЕЛИ() до НАЧАЛОПЕРИОДА() и РАЗНОСТЬДАТ(). Ниже — шпаргалка по функциям дат, сравнение без учёта времени, группировка по месяцам и дням недели, что на самом деле делает ВЫРАЗИТЬ(... КАК ДАТА) и как быть с датой, которая хранится строкой. Примеры — для платформы 8.3 и любой конфигурации; имена документов и реквизитов условные, подставьте свои.
| Функция | Что возвращает | Пример |
|---|---|---|
ДАТАВРЕМЯ(Г, М, Д[, Ч, Мин, С]) | дату-константу | ДАТАВРЕМЯ(2024, 1, 1) |
ГОД(Дата) | номер года | ГОД(Док.Дата) = 2024 |
КВАРТАЛ(Дата) | квартал, 1–4 | КВАРТАЛ(Док.Дата) = 4 |
МЕСЯЦ(Дата) | месяц, 1–12 | МЕСЯЦ(Док.Дата) = 12 |
ДЕНЬГОДА(Дата) | день года, 1–366 | ДЕНЬГОДА(Док.Дата) |
ДЕНЬ(Дата) | день месяца, 1–31 | ДЕНЬ(Док.Дата) = 1 |
НЕДЕЛЯ(Дата) | номер недели в году | НЕДЕЛЯ(Док.Дата) |
ДЕНЬНЕДЕЛИ(Дата) | 1 — понедельник … 7 — воскресенье | ДЕНЬНЕДЕЛИ(Док.Дата) В (6, 7) |
ЧАС, МИНУТА, СЕКУНДА | части времени | ЧАС(Док.Дата) >= 18 |
НАЧАЛОПЕРИОДА(Дата, Период) | первую секунду периода | НАЧАЛОПЕРИОДА(Док.Дата, МЕСЯЦ) |
КОНЕЦПЕРИОДА(Дата, Период) | последнюю секунду периода | КОНЕЦПЕРИОДА(&Дата, ДЕНЬ) |
ДОБАВИТЬКДАТЕ(Дата, Тип, Число) | дату, сдвинутую на N единиц | ДОБАВИТЬКДАТЕ(Док.Дата, ДЕНЬ, 10) |
РАЗНОСТЬДАТ(Дата1, Дата2, Тип) | разницу в заданных единицах | РАЗНОСТЬДАТ(Док.Дата, &ТекущаяДата, ДЕНЬ) |
Период для НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА пишется словом без кавычек: МИНУТА, ЧАС, ДЕНЬ, НЕДЕЛЯ, МЕСЯЦ, КВАРТАЛ, ГОД, ДЕКАДА, ПОЛУГОДИЕ. Неделя начинается с понедельника. Функции частей даты возвращают числа, поэтому сравнивать их нужно с числом: МЕСЯЦ(Док.Дата) = 12, а не со строкой «12».
Складывать и вычитать даты знаками + и - в запросе нельзя: для сдвига есть ДОБАВИТЬКДАТЕ, для разницы — РАЗНОСТЬДАТ. Обе функции подробно разобраны в статье о ДОБАВИТЬКДАТЕ в запросе.
Литерала даты вида '20240101', как во встроенном языке, в запросе нет — только функция ДАТАВРЕМЯ. Её аргументы — числа прямо в тексте запроса: год, месяц, день и при необходимости час, минута, секунда. Поле, параметр или выражение туда не подставить: запрос не пройдёт проверку синтаксиса.
ВЫБРАТЬ
Заказ.Ссылка КАК Заказ,
Заказ.Дата КАК Дата
ИЗ
Документ.ЗаказКлиента КАК Заказ
ГДЕ
Заказ.Дата >= ДАТАВРЕМЯ(2024, 1, 1)
И Заказ.Дата < ДАТАВРЕМЯ(2024, 4, 1)
И Заказ.ДатаОтгрузки = ДАТАВРЕМЯ(1, 1, 1)
Запрос выбирает заказы первого квартала 2024 года, у которых не заполнена дата отгрузки. ДАТАВРЕМЯ(1, 1, 1) — пустая дата, 01.01.0001 00:00:00: так в запросе проверяют незаполненный реквизит типа Дата. Конец квартала задан условием «меньше 1 апреля»: вариант «меньше или равно 31 марта» потеряет документы 31 марта с ненулевым временем.
Даты, зашитые в текст, годятся для разовой выборки в консоли, в отчётах и обработках период передают параметрами. Текущую дату тоже передают параметром и берут на сервере через ТекущаяДатаСеанса(): она учитывает часовой пояс сеанса, а ТекущаяДата() возвращает время компьютера, где выполняется код.
// Код на сервере. Период — реквизит типа СтандартныйПериод.
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(Период.ДатаНачала));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(Период.ДатаОкончания));
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("ПустаяДата", Дата(1, 1, 1));
Реквизит Дата у документа хранит и время. Если передать в параметр 15.01.2024 и написать Док.Дата = &Дата, найдутся только документы с временем 00:00:00 — на практике почти никакие. День нужно задавать интервалом.
| Условие | Когда использовать |
|---|---|
Док.Дата МЕЖДУ &НачалоДня И &КонецДня | Основной вариант: границы считаются в коде функциями НачалоДня() и КонецДня(), индекс по дате работает. |
Док.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ) И КОНЕЦПЕРИОДА(&Дата, ДЕНЬ) | То же, но границы считает запрос — удобно, когда параметр приходит «как есть», например из пользовательских настроек СКД. Функция применяется к параметру, а не к полю, и индексу не мешает. |
НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &Дата | Результат верный, но функция считается для каждой строки, и СУБД, как правило, не может использовать индекс по дате. На больших журналах это заметно медленнее. |
Когда нужно сравнить даты двух полей без времени, функция над полями неизбежна. Пример — оплаты, поступившие в тот же день, что и отгрузка тому же контрагенту:
ВЫБРАТЬ
Реализация.Ссылка КАК Реализация,
Оплата.Ссылка КАК Оплата
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеБезналичныхДенежныхСредств КАК Оплата
ПО (Оплата.Контрагент = Реализация.Контрагент)
И (НАЧАЛОПЕРИОДА(Оплата.Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(Реализация.Дата, ДЕНЬ))
ГДЕ
Реализация.Проведен
И Оплата.Проведен
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И Оплата.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Ограничьте периодом обе таблицы. Если границы выровнены по дням (НачалоДня() и КонецДня()), условие на Оплата.Дата результат не меняет, зато СУБД отберёт оплаты по индексу, и функции в условии соединения посчитаются для небольшого набора строк, а не для всех оплат за всё время. Как ещё отбирать документы за период и по реквизитам, описано в статье о запросах к документам.
Для помесячной разбивки есть два пути с разным результатом. НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ) возвращает дату — 01.02.2024, 01.03.2024 — и отличает январь 2024 от января 2025. МЕСЯЦ(Дата) возвращает число от 1 до 12 и сложит январи разных лет в одну строку. Для динамики по месяцам берите первый вариант, для сезонности («все декабри за пять лет») — второй вместе с ГОД().
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ) КАК НачалоМесяца,
СУММА(Реализация.СуммаДокумента) КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ)
УПОРЯДОЧИТЬ ПО
НачалоМесяца
В СГРУППИРОВАТЬ ПО выражение повторяется целиком, псевдоним там не подойдёт; в УПОРЯДОЧИТЬ ПО псевдоним допустим. Если те же данные уже лежат в регистре накопления, помесячные итоги проще взять из виртуальной таблицы Обороты с периодичностью Месяц: поле Период там уже содержит начало месяца.
ДЕНЬНЕДЕЛИ() возвращает число: 1 — понедельник, 7 — воскресенье. Документы, созданные в выходные, отбираются условием ДЕНЬНЕДЕЛИ(Док.Дата) В (6, 7), вечерние — через ЧАС(Док.Дата) >= 18. Название дня в запросе получают конструкцией ВЫБОР. Готовая функция для отчёта «выручка по дням недели» покажет владельцу магазина, в какие дни продажи проседают:
// Выручка по дням недели за период.
// Размещение: серверный общий модуль или модуль менеджера отчёта.
// Имена объектов условные: документ РеализацияТоваровУслуг с реквизитом СуммаДокумента.
Функция ВыручкаПоДнямНедели(Знач НачалоПериода, Знач КонецПериода) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.НомерДня КАК НомерДня,
| ВЫБОР
| КОГДА Продажи.НомерДня = 1 ТОГДА ""Понедельник""
| КОГДА Продажи.НомерДня = 2 ТОГДА ""Вторник""
| КОГДА Продажи.НомерДня = 3 ТОГДА ""Среда""
| КОГДА Продажи.НомерДня = 4 ТОГДА ""Четверг""
| КОГДА Продажи.НомерДня = 5 ТОГДА ""Пятница""
| КОГДА Продажи.НомерДня = 6 ТОГДА ""Суббота""
| ИНАЧЕ ""Воскресенье""
| КОНЕЦ КАК НазваниеДня,
| Продажи.Документов КАК Документов,
| Продажи.Сумма КАК Сумма
|ИЗ
| (ВЫБРАТЬ
| ДЕНЬНЕДЕЛИ(Реализация.Дата) КАК НомерДня,
| КОЛИЧЕСТВО(Реализация.Ссылка) КАК Документов,
| СУММА(Реализация.СуммаДокумента) КАК Сумма
| ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
| ГДЕ
| Реализация.Проведен
| И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|
| СГРУППИРОВАТЬ ПО
| ДЕНЬНЕДЕЛИ(Реализация.Дата)) КАК Продажи
|
|УПОРЯДОЧИТЬ ПО
| НомерДня";
// Границы дня считаем в коде: условие по полю Дата остаётся простым
// и не зависит от того, какое время выбрал пользователь.
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(НачалоПериода));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(КонецПериода));
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
ДЕНЬНЕДЕЛИ(Реализация.Дата), а название дня добавляет внешний запрос. Так не приходится повторять громоздкий ВЫБОР в СГРУППИРОВАТЬ ПО.НачалоДня() и КонецДня(). Даже если пользователь выбрал период с временем, граничные дни войдут целиком: МЕЖДУ включает обе границы, а КонецДня() даёт 23:59:59.Из модуля формы функцию вызывают в процедуре с директивой &НаСервере или &НаСервереБезКонтекста. Общий синтаксис разобран в статье о языке запросов 1С.
ВЫРАЗИТЬ в языке запросов не переводит значение из одного типа в другой: он выделяет нужный тип из поля составного типа или меняет квалификаторы, например длину строки. Типичный случай — дополнительные реквизиты БСП: колонка Значение там составного типа, и дата хранится вперемешку со строками, числами и ссылками. Чтобы сравнить её с датой, нужно явно выделить часть «Дата»:
ВЫБРАТЬ
ДопРеквизиты.Ссылка КАК Договор,
ВЫРАЗИТЬ(ДопРеквизиты.Значение КАК ДАТА) КАК ДатаОкончания
ИЗ
Справочник.ДоговорыКонтрагентов.ДополнительныеРеквизиты КАК ДопРеквизиты
ГДЕ
ДопРеквизиты.Свойство = &СвойствоДатаОкончания
И ВЫРАЗИТЬ(ДопРеквизиты.Значение КАК ДАТА) < &ТекущаяДата
Для значения другого типа ВЫРАЗИТЬ вернёт NULL, и строка в результат не попадёт.
Выразить как дату без времени не получится: ВЫРАЗИТЬ время не обрезает. Состав даты (дата, время, дата и время) задаётся квалификатором реквизита в конфигураторе, а не в запросе. Чтобы отбросить время, используйте НАЧАЛОПЕРИОДА(Поле, ДЕНЬ).
Выразить как дату и время отдельно не нужно: ДАТАВРЕМЯ — функция, а не тип, а ВЫРАЗИТЬ(... КАК ДАТА) и так возвращает значение вместе с хранившимся в нём временем.
Запрос вида ВЫРАЗИТЬ(Т.ДатаСтрокой КАК ДАТА) выполнен не будет: разбирать строку в дату язык запросов не умеет. Функции, превращающей строку в число, в языке запросов тоже нет: СТРОКА() из 8.3.20 работает только в обратную сторону. Собрать дату из кусочков ПОДСТРОКА формально можно, если переводить каждую цифру в число громоздким ВЫБОР, но такой запрос тяжело читать и сопровождать.
Обходных путей три.
ДОБАВИТЬКДАТЕ от базовой даты.Второй вариант в коде:
// Серверный общий модуль. ДанныеФайла — таблица значений с колонками
// НомерСтрокой и ДатаСтрокой: строки вида "15.01.2024", например прочитанные из Excel.
Функция НайтиПоступленияПоРеестру(ДанныеФайла) Экспорт
Реестр = Новый ТаблицаЗначений;
Реестр.Колонки.Добавить("Номер", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(30)));
Реестр.Колонки.Добавить("ДатаДокумента",
Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)));
Для Каждого СтрокаФайла Из ДанныеФайла Цикл
НоваяСтрока = Реестр.Добавить();
НоваяСтрока.Номер = СокрЛП(СтрокаФайла.НомерСтрокой);
НоваяСтрока.ДатаДокумента = ДатаИзСтроки(СтрокаФайла.ДатаСтрокой);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реестр.Номер КАК Номер,
| Реестр.ДатаДокумента КАК ДатаДокумента
|ПОМЕСТИТЬ ВТ_Реестр
|ИЗ
| &Реестр КАК Реестр
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Реестр.Номер КАК Номер,
| ВТ_Реестр.ДатаДокумента КАК ДатаДокумента,
| Поступление.Ссылка КАК Документ
|ИЗ
| ВТ_Реестр КАК ВТ_Реестр
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг КАК Поступление
| ПО (Поступление.НомерВходящегоДокумента = ВТ_Реестр.Номер)
| И (НАЧАЛОПЕРИОДА(Поступление.ДатаВходящегоДокумента, ДЕНЬ) = ВТ_Реестр.ДатаДокумента)
| И (ВТ_Реестр.ДатаДокумента <> ДАТАВРЕМЯ(1, 1, 1))
| И (НЕ Поступление.ПометкаУдаления)";
Запрос.УстановитьПараметр("Реестр", Реестр);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
// Разбирает строку "ДД.ММ.ГГГГ". Если строка не похожа на дату, возвращает пустую дату.
Функция ДатаИзСтроки(Знач Текст)
Части = СтрРазделить(СокрЛП(Текст), ".", Ложь);
Если Части.Количество() <> 3 Или СтрДлина(Части[2]) <> 4 Тогда
Возврат Дата(1, 1, 1);
КонецЕсли;
Попытка
Возврат Дата(Число(Части[2]), Число(Части[1]), Число(Части[0]));
Исключение
Возврат Дата(1, 1, 1);
КонецПопытки;
КонецФункции
Колонки таблицы типизированы явно: без описания типов запрос не поместит таблицу во временную и остановится с ошибкой «Тип не может быть выбран в запросе». СтрРазделить() доступна с 8.3.6. Нераспознанная дата становится пустой, и условие <> ДАТАВРЕМЯ(1, 1, 1) не даёт такой строке ложно совпасть с поступлением без входящей даты: поле Документ у неё будет NULL — покажите такие строки пользователю. Помеченные на удаление поступления в сверку не попадают. Двузначный год («15.01.24») функция намеренно не принимает. Подробнее о передаче таблиц в запрос — в статье о таблице значений в запросе.
Третий вариант — для регистра планов, где год и месяц хранятся числовыми измерениями:
ВЫБРАТЬ
Планы.Номенклатура КАК Номенклатура,
ДОБАВИТЬКДАТЕ(
ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(2000, 1, 1), ГОД, Планы.НомерГода - 2000),
МЕСЯЦ, Планы.НомерМесяца - 1) КАК НачалоМесяца,
Планы.Количество КАК Количество
ИЗ
РегистрСведений.ПланыПродажПоМесяцам КАК Планы
Особый случай — строка в формате ГГГГММДД («20240115»). Её можно не преобразовывать: при фиксированной длине сравнение строк совпадает с хронологическим, и условие Т.ДатаСтрокой МЕЖДУ "20240101" И "20240131" отберёт январь. Строки неограниченной длины в запросе сравнивать нельзя, поэтому такой реквизит сначала ограничьте: ВЫРАЗИТЬ(Т.ДатаСтрокой КАК СТРОКА(8)) МЕЖДУ "20240101" И "20240131".
Док.Дата <= &Дата, где в параметре дата без времени. Документы последнего дня с временем позже 00:00:00 выпадают. Передавайте КонецДня(Дата).Док.Дата + 1 или &Дата - 86400 запрос не примет. Используйте ДОБАВИТЬКДАТЕ.ДАТАВРЕМЯ. ДАТАВРЕМЯ(&Год, 1, 1) — синтаксическая ошибка. Соберите дату в коде и передайте параметром.УстановитьПараметр() должно совпадать с именем после &.Новый Запрос на клиенте падает с ошибкой «Тип не определен (Запрос)». Запрос выполняется только на сервере.Остатки(&Дата) возвращает остаток на начало указанного момента. Чтобы учесть движения за весь день, передайте Новый Граница(КонецДня(Дата), ВидГраницы.Включая).NULL, а не ДАТАВРЕМЯ(1, 1, 1). Проверяйте ЕСТЬ NULL или приводите через ЕСТЬNULL(Поле, ДАТАВРЕМЯ(1, 1, 1)).Функции текущей даты в языке запросов нет. Передайте её параметром: Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДатаСеанса()). В вычисляемых полях СКД есть своя функция ТекущаяДата() языка выражений.
НАЧАЛОПЕРИОДА(Поле, ДЕНЬ) — время станет 00:00:00. ВЫРАЗИТЬ(Поле КАК ДАТА) для этого не подходит: время он не отбрасывает.
Запрос возвращает только те даты, которые есть в данных. Пропуски дополняет ключевое слово ПЕРИОДАМИ в секции ИТОГИ, например ИТОГИ СУММА(Сумма) ПО Период ПЕРИОДАМИ(ДЕНЬ, &НачалоПериода, &КонецПериода), но только при обходе Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Период", "ВСЕ"): без третьего параметра "ВСЕ" пустые дни не появятся. В СКД для этого есть настройка дополнения периодов у поля группировки. Или добавьте недостающие строки в коде.
С 8.3.20 есть функция СТРОКА(), но вид результата зависит от национальных настроек. Надёжнее выбрать дату как есть и отформатировать при выводе функцией Формат() или в настройках СКД. Остальные строковые функции собраны в статье о строках в запросе.
В запросе набора данных — да, это тот же язык запросов. В вычисляемых полях и ресурсах действует язык выражений СКД: там функции пишутся похоже, но период передаётся строкой — НачалоПериода(Дата, "Месяц").
Если отчёт с периодами собирает данные из нескольких регистров, должен считать рабочие дни или сверять даты из чужих файлов, быстрее отдать его программисту — посмотрите услугу отчёты и обработки 1С. Опишите задачу на странице «Разместить задание»: оценка бесплатная, ответ в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.