Отбор по датам в языке запросов 1С 8.3: МЕЖДУ, НАЧАЛОПЕРИОДА, Граница и виртуальные таблицы
Содержание
Период в запросе 1С задают двумя параметрами и условием ГДЕ Док.Дата МЕЖДУ &НачалоПериода И &КонецПериода. Перед выполнением в параметры передают НачалоДня(ДатаНачала) и КонецДня(ДатаОкончания), иначе последний день периода выпадет из выборки. Для регистров дату передают не в ГДЕ, а в параметры виртуальной таблицы: Обороты(&НачалоПериода, &КонецПериода), Остатки(&Дата), СрезПоследних(&Дата).
Всё ниже относится к языку запросов платформы 1С:Предприятие 8.3 и одинаково работает в любой конфигурации: БП 3.0, ЗУП 3.1, УТ 11 или самописной. Отличаются только имена объектов метаданных.
Условие на период в секции ГДЕ: базовый синтаксис
Поле типа Дата в 1С хранит дату и время с точностью до секунды. Поэтому ограничить выборку периодом можно тремя равнозначными способами.
// 1. МЕЖДУ — обе границы включаются
ВЫБРАТЬ
Реализация.Ссылка,
Реализация.Дата,
Реализация.СуммаДокумента
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
// 2. То же самое через сравнения
ГДЕ
Реализация.Дата >= &НачалоПериода
И Реализация.Дата <= &КонецПериода
// 3. Полуоткрытый интервал: конец не включается
ГДЕ
Реализация.Дата >= &НачалоПериода
И Реализация.Дата < &НачалоСледующегоПериода
Третий вариант удобен, когда конец периода вычисляется сложением: начало следующего месяца получить проще, чем 23:59:59 последнего дня.
Параметры передают из кода:
Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаОкончания));
Результат = Запрос.Выполнить();
Даты прямо в тексте запроса
Если период фиксирован, дату можно записать литералом ДАТАВРЕМЯ(год, месяц, день, час, минута, секунда). Время можно опустить, тогда это полночь:
ГДЕ
Реализация.Дата МЕЖДУ ДАТАВРЕМЯ(2024, 1, 1) И ДАТАВРЕМЯ(2024, 3, 31, 23, 59, 59)
Литералы годятся для разовых запросов в консоли. В коде отчётов и обработок используйте параметры: запрос с параметрами читается проще и не требует сборки строки.
Текущей даты в языке запросов нет
Функции вроде ТЕКУЩАЯДАТА() в языке запросов не существует. Текущую дату получают в коде через ТекущаяДатаСеанса() и передают параметром. Это же касается «последних 30 дней»: вычислите начало в коде или в самом запросе от переданного параметра.
Начало и конец периода: функции НАЧАЛОПЕРИОДА, КОНЕЦПЕРИОДА, ДОБАВИТЬКДАТЕ
Язык запросов умеет сам округлять дату до границ периода. Это избавляет от лишнего кода и защищает от ошибки с последним днём.
| Функция | Что делает | Пример результата для 15.05.2024 10:30:00 |
|---|---|---|
НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ) | начало месяца | 01.05.2024 00:00:00 |
КОНЕЦПЕРИОДА(Дата, МЕСЯЦ) | последняя секунда месяца | 31.05.2024 23:59:59 |
НАЧАЛОПЕРИОДА(Дата, КВАРТАЛ) | начало квартала | 01.04.2024 00:00:00 |
КОНЕЦПЕРИОДА(Дата, ДЕНЬ) | конец дня | 15.05.2024 23:59:59 |
ДОБАВИТЬКДАТЕ(Дата, ДЕНЬ, -7) | сдвиг на 7 дней назад | 08.05.2024 10:30:00 |
РАЗНОСТЬДАТ(Д1, Д2, ДЕНЬ) | число дней между датами | целое число |
Вид периода для НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА: МИНУТА, ЧАС, ДЕНЬ, НЕДЕЛЯ, МЕСЯЦ, КВАРТАЛ, ГОД, ДЕКАДА, ПОЛУГОДИЕ.
Месяц по одной дате
Пользователь выбрал любую дату внутри месяца, а отобрать нужно весь месяц. Округление делается прямо в условии, к параметру, а не к полю:
ГДЕ
Реализация.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Дата, МЕСЯЦ)
И КОНЕЦПЕРИОДА(&Дата, МЕСЯЦ)
Последние N дней
ГДЕ
Реализация.Дата >= ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&ТекущаяДата, ДЕНЬ), ДЕНЬ, -30)
Параметр &ТекущаяДата передайте из кода значением ТекущаяДатаСеанса().
Группировка по месяцам
Те же функции работают в списке полей. Так строится свод продаж по месяцам за год:
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ) КАК Месяц,
СУММА(Реализация.СуммаДокумента) КАК Сумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Дата, ГОД) И КОНЕЦПЕРИОДА(&Дата, ГОД)
И Реализация.Проведен
СГРУППИРОВАТЬ ПО
НАЧАЛОПЕРИОДА(Реализация.Дата, МЕСЯЦ)
Есть и функции-«извлекатели»: ГОД(), КВАРТАЛ(), МЕСЯЦ(), ДЕНЬ(), ДЕНЬНЕДЕЛИ(), ЧАС(). Они возвращают число и полезны в полях, но в условиях их лучше не применять к полю (почему — в разделе о скорости).
Интервалы: МЕЖДУ, полуоткрытый период и отбор в СКД
Отдельного оператора ВНУТРИ в языке запросов 1С нет. Попадание даты в интервал выражают через МЕЖДУ или пару сравнений. Разница только во включении границ.
| Нужно | Условие |
|---|---|
| обе границы включаются | Дата МЕЖДУ &Н И &К |
| конец не включается | Дата >= &Н И Дата < &К |
| начало не включается | Дата > &Н И Дата <= &К |
| только до даты | Дата <= &К |
| вне периода | НЕ Дата МЕЖДУ &Н И &К |
Пересечение двух периодов
Частая задача — найти договоры, приказы или скидки, действующие хотя бы день в выбранном периоде. У записи есть свои ДатаНачала и ДатаОкончания. Условие пересечения:
ГДЕ
Т.ДатаНачала <= &КонецПериода
И (Т.ДатаОкончания >= &НачалоПериода
ИЛИ Т.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1))
Вторая строка с пустой датой нужна для бессрочных записей: у них дата окончания не заполнена.
Период в схеме компоновки данных
В СКД период обычно задают одним параметром Период с типом СтандартныйПериод. Пользователь выбирает «Этот месяц» или произвольные даты, а параметры запроса заполняются выражениями на вкладке «Параметры»:
НачалоПериода— выражение&Период.ДатаНачала;КонецПериода— выражение&Период.ДатаОкончания.
Обоим служебным параметрам снимите флаг доступности, чтобы пользователь видел только поле Период. Если нужен отбор по дате без параметров, в настройках СКД есть виды сравнения «В интервале» и «В интервале включая границы». Они различаются именно включением крайних дат.
Период в виртуальных таблицах регистров
Для регистров правило одно: период передавайте в параметры виртуальной таблицы, а не в ГДЕ. Платформа тогда берёт готовые итоги и не перебирает все движения.
Обороты за период
ВЫБРАТЬ
Обороты.Номенклатура,
Обороты.КоличествоПриход,
Обороты.КоличествоРасход
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(
&НачалоПериода,
&КонецПериода,
,
Склад = &Склад) КАК Обороты
Имя регистра здесь условное, подставьте своё. Обе даты у Обороты включаются. Третий параметр — периодичность (Месяц, День, Регистратор, Запись и другие). С ней в выборке появляется поле Период, и обороты разбиваются по интервалам.
Остатки на дату
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Момент, Склад = &Склад) КАК Остатки
Тонкость в том, что дата в Остатки — это граница, которая не включается. Остаток на 01.06.2024 00:00:00 — это остаток на начало 1 июня. Чтобы получить остаток на конец дня с учётом всех его документов, передайте объект Граница:
Запрос.УстановитьПараметр("Момент",
Новый Граница(КонецДня(Дата), ВидГраницы.Включая));
Остатки и обороты вместе
ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Периодичность, МетодДополнения, Условие) выдаёт начальный остаток, приход, расход и конечный остаток. Метод дополнения ДвиженияИГраницыПериода добавляет строки и для дней без движений, если задана периодичность. Это удобно для ежедневной ведомости остатков.
Регистры сведений
СрезПоследних(&Дата, Условие) возвращает последнюю запись на дату включительно: запись с периодом ровно &Дата в срез попадёт. СрезПервых(&Дата, Условие) — первую запись с этой даты и позже. Так получают курс валюты, цену или оклад на конкретный день.
Объект Граница и момент документа
| Что передать | Как | Когда |
|---|---|---|
| дату | &Дата | обороты, срез последних |
| границу включая | Новый Граница(КонецДня(Дата), ВидГраницы.Включая) | остаток на конец дня |
| границу исключая | Новый Граница(Дата, ВидГраницы.Исключая) | исключить движения ровно на эту секунду |
| момент документа | Документ.МоментВремени() | остаток до документа при его проведении |
| граница по документу | Новый Граница(Документ.МоментВремени(), ВидГраницы.Включая) | остаток с учётом самого документа |
Момент времени нужен, когда в одной секунде несколько документов. Голая дата их не различает, а момент учитывает ещё и ссылку.
Время и секунды: почему пропадает последний день
Самая частая жалоба: «отчёт за май не показывает документы 31 мая». Причина — время.
Поле ввода даты без времени даёт значение 31.05.2024 0:00:00. Документ от 31.05.2024 14:20:00 больше этой даты, поэтому условие Дата <= &КонецПериода его отбрасывает. Есть три способа починить:
- 🔧 В коде:
КонецДня(ДатаОкончания)передУстановитьПараметр. - 🔧 В запросе:
МЕЖДУ &НачалоПериода И КОНЕЦПЕРИОДА(&КонецПериода, ДЕНЬ). - 🔧 Полуоткрытый интервал:
Дата < ДОБАВИТЬКДАТЕ(&КонецПериода, ДЕНЬ, 1)при условии, что в параметре полночь.
Обратная проблема с остатками. Документы закрытия месяца в типовых конфигурациях часто имеют время 23:59:59. Если передать в Остатки значение КонецДня(Дата) как обычную дату, граница не включится, и движения ровно в 23:59:59 выпадут. Поэтому для остатков на конец дня используйте Граница с ВидГраницы.Включая.
Пустая дата
Незаполненная дата в 1С — это 01.01.0001 0:00:00, в запросе ДАТАВРЕМЯ(1, 1, 1). Если пользователь не указал начало периода, условие Дата >= &НачалоПериода с пустым параметром пропустит все записи. С пустым концом наоборот — не вернёт ничего. Проверяйте параметры до выполнения запроса.
Необязательный период
Если период может быть не задан, проще всего собрать условие в коде и добавлять его к тексту запроса только при заполненных датах. Вариант с ИЛИ &НачалоПериода = ДАТАВРЕМЯ(1, 1, 1) работает, но мешает использовать индекс.
Как ускорить выборку по датам
Таблица документов по умолчанию проиндексирована по дате. Регистры — по периоду. Индекс помогает только при «чистом» сравнении поля с диапазоном.
- 🐢
ГДЕ ГОД(Док.Дата) = 2024— функция от поля, индекс не используется, СУБД перебирает всю таблицу. - 🐢
ГДЕ НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &День— то же самое. - 🚀
ГДЕ Док.Дата МЕЖДУ &Н И &К— диапазон по индексу. - 🐢 Отбор периода в
ГДЕпосле виртуальной таблицыОборотыбез параметров — сначала считаются обороты за всё время. - 🚀 Период в параметрах
Обороты(&Н, &К). - 🐢
ИЛИв условии с датой, особенно с проверкой пустого параметра.
Ещё несколько приёмов из практики:
- Сначала сузьте выборку по дате, потом соединяйте с другими таблицами. Для тяжёлых запросов вынесите отбор во временную таблицу
ПОМЕСТИТЬ. - В условии виртуальной таблицы указывайте только измерения регистра. Отбор по реквизитам связанных справочников там тоже возможен, но платформа превращает его в соединение и выигрыш теряется.
- Для остатков на произвольную дату платформа берёт ближайшие рассчитанные итоги и досчитывает движения. Следите, чтобы итоги регистра были рассчитаны до актуального периода. Иначе остаток на сегодня будет считаться от давней даты.
- Отбирая документы по дате, добавьте условие
Проведен, если непроведённые не нужны. Отбор станет и правильнее, и короче.
Типичные промахи в условиях по дате
Кроме выпадающего последнего дня, есть ошибки, которые замечают не сразу.
- ❌ Перепутаны параметры.
МЕЖДУ &КонецПериода И &НачалоПериодане вызывает ошибки, а возвращает пустой результат.МЕЖДУне меняет границы местами. - ❌ Параметр строкой. Если в
УстановитьПараметрпередать строку"01.05.2024", сравнения с датой не будет. Передавайте значение типаДата, напримерДата(2024, 5, 1). - ❌ Граница в ГДЕ. Объект
Границапонимают только параметры виртуальных таблиц. В сравнении с полем вГДЕнужна обычная дата. - ❌ Период документа вместо периода регистра. Отбор по
Регистратор.ДатавместоПериодмедленнее и даёт другой результат, если дата движения отличается от даты документа. - ❌ Сравнение с
ТекущаяДатаСеанса()внутри текста. Такой функции в запросе нет, будет ошибка синтаксиса. Только параметр. - ❌ Остаток на начало периода из
Остатки(&КонецПериода). Для начального остатка передавайте начало периода, а лучше берите полеНачальныйОстатокизОстаткиИОбороты.
Частые вопросы
Включает ли МЕЖДУ крайние даты?
Дата МЕЖДУ &Н И &К равносильно Дата >= &Н И Дата <= &К.Как отобрать документы за один день?
ГДЕ Док.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&День, ДЕНЬ) И КОНЕЦПЕРИОДА(&День, ДЕНЬ). Сравнение Док.Дата = &День найдёт только документы ровно с временем 00:00:00.Как получить остаток на начало месяца?
Остатки дату НачалоМесяца(Дата). Граница не включается, поэтому движения первого числа в остаток не попадут — это и есть остаток на начало.Можно ли задать период для нескольких виртуальных таблиц одним параметром?
&НачалоПериода можно указать в разных таблицах одного запроса. Значение устанавливается один раз через УстановитьПараметр.Как отобрать тот же месяц прошлого года?
МЕЖДУ НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, ГОД, -1), МЕСЯЦ) И КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, ГОД, -1), МЕСЯЦ).