Отбор по датам в языке запросов 1С 8.3: МЕЖДУ, НАЧАЛОПЕРИОДА, Граница и виртуальные таблицы

· 9 мин

Содержание

Период в запросе 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. 🔧 В коде: КонецДня(ДатаОкончания) перед УстановитьПараметр.
  2. 🔧 В запросе: МЕЖДУ &НачалоПериода И КОНЕЦПЕРИОДА(&КонецПериода, ДЕНЬ).
  3. 🔧 Полуоткрытый интервал: Дата < ДОБАВИТЬКДАТЕ(&КонецПериода, ДЕНЬ, 1) при условии, что в параметре полночь.

Обратная проблема с остатками. Документы закрытия месяца в типовых конфигурациях часто имеют время 23:59:59. Если передать в Остатки значение КонецДня(Дата) как обычную дату, граница не включится, и движения ровно в 23:59:59 выпадут. Поэтому для остатков на конец дня используйте Граница с ВидГраницы.Включая.

Пустая дата

Незаполненная дата в 1С — это 01.01.0001 0:00:00, в запросе ДАТАВРЕМЯ(1, 1, 1). Если пользователь не указал начало периода, условие Дата >= &НачалоПериода с пустым параметром пропустит все записи. С пустым концом наоборот — не вернёт ничего. Проверяйте параметры до выполнения запроса.

Необязательный период

Если период может быть не задан, проще всего собрать условие в коде и добавлять его к тексту запроса только при заполненных датах. Вариант с ИЛИ &НачалоПериода = ДАТАВРЕМЯ(1, 1, 1) работает, но мешает использовать индекс.

Как ускорить выборку по датам

Таблица документов по умолчанию проиндексирована по дате. Регистры — по периоду. Индекс помогает только при «чистом» сравнении поля с диапазоном.

  • 🐢 ГДЕ ГОД(Док.Дата) = 2024 — функция от поля, индекс не используется, СУБД перебирает всю таблицу.
  • 🐢 ГДЕ НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &День — то же самое.
  • 🚀 ГДЕ Док.Дата МЕЖДУ &Н И &К — диапазон по индексу.
  • 🐢 Отбор периода в ГДЕ после виртуальной таблицы Обороты без параметров — сначала считаются обороты за всё время.
  • 🚀 Период в параметрах Обороты(&Н, &К).
  • 🐢 ИЛИ в условии с датой, особенно с проверкой пустого параметра.

Ещё несколько приёмов из практики:

  1. Сначала сузьте выборку по дате, потом соединяйте с другими таблицами. Для тяжёлых запросов вынесите отбор во временную таблицу ПОМЕСТИТЬ.
  2. В условии виртуальной таблицы указывайте только измерения регистра. Отбор по реквизитам связанных справочников там тоже возможен, но платформа превращает его в соединение и выигрыш теряется.
  3. Для остатков на произвольную дату платформа берёт ближайшие рассчитанные итоги и досчитывает движения. Следите, чтобы итоги регистра были рассчитаны до актуального периода. Иначе остаток на сегодня будет считаться от давней даты.
  4. Отбирая документы по дате, добавьте условие Проведен, если непроведённые не нужны. Отбор станет и правильнее, и короче.
Диагностика: выберите симптом

Типичные промахи в условиях по дате

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

  • ❌ Перепутаны параметры. МЕЖДУ &КонецПериода И &НачалоПериода не вызывает ошибки, а возвращает пустой результат. МЕЖДУ не меняет границы местами.
  • ❌ Параметр строкой. Если в УстановитьПараметр передать строку "01.05.2024", сравнения с датой не будет. Передавайте значение типа Дата, например Дата(2024, 5, 1).
  • ❌ Граница в ГДЕ. Объект Граница понимают только параметры виртуальных таблиц. В сравнении с полем в ГДЕ нужна обычная дата.
  • ❌ Период документа вместо периода регистра. Отбор по Регистратор.Дата вместо Период медленнее и даёт другой результат, если дата движения отличается от даты документа.
  • ❌ Сравнение с ТекущаяДатаСеанса() внутри текста. Такой функции в запросе нет, будет ошибка синтаксиса. Только параметр.
  • ❌ Остаток на начало периода из Остатки(&КонецПериода). Для начального остатка передавайте начало периода, а лучше берите поле НачальныйОстаток из ОстаткиИОбороты.

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

Включает ли МЕЖДУ крайние даты?
Да, обе границы включаются. Дата МЕЖДУ &Н И &К равносильно Дата >= &Н И Дата <= &К.
Как отобрать документы за один день?
ГДЕ Док.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&День, ДЕНЬ) И КОНЕЦПЕРИОДА(&День, ДЕНЬ). Сравнение Док.Дата = &День найдёт только документы ровно с временем 00:00:00.
Как получить остаток на начало месяца?
Передайте в Остатки дату НачалоМесяца(Дата). Граница не включается, поэтому движения первого числа в остаток не попадут — это и есть остаток на начало.
Можно ли задать период для нескольких виртуальных таблиц одним параметром?
Да. Один параметр &НачалоПериода можно указать в разных таблицах одного запроса. Значение устанавливается один раз через УстановитьПараметр.
Как отобрать тот же месяц прошлого года?
Сдвиньте параметр на год и округлите: МЕЖДУ НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, ГОД, -1), МЕСЯЦ) И КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, ГОД, -1), МЕСЯЦ).