BETWEEN в SQL и фильтр по датам: как не потерять последний день
Как правильно фильтровать даты в SQL: BETWEEN, полуоткрытые интервалы, timestamp, часовые пояса и полные календарные периоды.
Содержание статьи
BETWEEN включителен с обеих сторон. Для DATE это иногда удобно, но для timestamp условие до 2026-08-31 00:00:00 исключит почти весь последний день. Ошибка особенно опасна в ежедневных отчётах: цифра выглядит правдоподобно, а граница периода уже неверна. Разберём вопрос на одном запросе: Почему заказ за последний день иногда не попадает в отчёт и какой интервал считать безопасным?
Симптом в отчёте
Почему заказ за последний день иногда не попадает в отчёт и какой интервал считать безопасным?
Отчёт за июль должен включать paid_at >= 2026-07-01 00:00 и paid_at < 2026-08-01 00:00. Если использовать BETWEEN DATE 2026-07-01 AND DATE 2026-07-31 для timestamp, база может привести конец к полуночи и потерять заказы 31 июля после 00:00.
Для timestamp в аналитике по периодам используй >= start AND < end. BETWEEN оставляй для действительно включительных границ, когда тип и смысл интервала это допускают. Всегда фиксируй timezone и статус текущего периода.
Что на самом деле делает SQL
Для временных рядов удобно использовать полуоткрытый интервал: >= start и < end. Тогда месяц задаётся от первого дня до первого дня следующего месяца, без ручного вычисления 23:59:59. Важно сравнивать даты в согласованном часовом поясе и не смешивать локальные timestamp с UTC.
Храни границы периода отдельно и передавай их параметрами. Приведи бизнесовую дату в нужный timezone до группировки. Проверь строку ровно на start, ровно на end и в последнюю секунду периода. Для календарных периодов вычисляй next_period_start.
- Зафиксируй одну строку результата.
- Отдели условия по исходным строкам от условий по рассчитанным значениям.
- Назови период, ключи связи и правило для повторов.
Исправленный запрос
Названия таблиц здесь условные. При переносе сохрани зерно и бизнес-условия, а не буквальный синтаксис примера.
SELECT DATE_TRUNC('day', paid_at AT TIME ZONE 'Europe/Moscow') AS day,
COUNT(*) AS orders, SUM(amount) AS revenue
FROM orders
WHERE paid_at >= TIMESTAMPTZ '2026-07-01 00:00:00+03'
AND paid_at < TIMESTAMPTZ '2026-08-01 00:00:00+03'
GROUP BY 1 ORDER BY 1;Контрольные строки
Собери пять-десять строк, где ответ можно получить вручную. Добавь NULL, дубль, объект без связанной записи и значение на границе периода. Затем сравни число строк и уникальных ключей до и после спорного шага.
Выполненный без ошибки запрос ещё не является правильным. Другой аналитик должен суметь восстановить из него источник, фильтр, grain и ожидаемое поведение на крайних случаях.
| Условие | Левая граница | Правая граница |
|---|---|---|
| >= start AND < end | включена | исключена |
| BETWEEN date AND date | включена | включена |
| timestamp до полуночи | включена | теряет день |
| date после timezone | зависит от приведения | проверить явно |
Где появится ошибка
Не используй 23:59:59: миллисекунды всё равно останутся за пределами. Не кастуй timestamp к date без понимания timezone. BETWEEN для чисел и дат может быть уместен, но его границы должны быть очевидны. Неполный текущий день нельзя сравнивать с полными днями.
Для timestamp в аналитике по периодам используй >= start AND < end. BETWEEN оставляй для действительно включительных границ, когда тип и смысл интервала это допускают. Всегда фиксируй timezone и статус текущего периода.
Условный пример: включительная полуночная граница исключает почти весь последний день.
Попробуй на своей схеме
Сделай тестовую таблицу с четырьмя timestamp на границах месяца. Прогони BETWEEN и полуоткрытый интервал. Затем сравни дневную сумму с выгрузкой из источника в часовом поясе бизнеса.
- Сначала запиши ожидаемый результат словами.
- Проверь запрос на маленькой контрольной выборке.
- Объясни, что запрос считает и чего не доказывает.
Что оставить в документации
Для timestamp в аналитике по периодам используй >= start AND < end. BETWEEN оставляй для действительно включительных границ, когда тип и смысл интервала это допускают. Всегда фиксируй timezone и статус текущего периода.
Материалы по теме

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

CAST и типы данных в SQL: почему конверсия равна нулю
Целочисленное деление, numeric против float, текст в число и дату, приведение типов в WHERE и индексы: как готовить данные так, чтобы расчёт не врал молча.
Собеседование аналитика данных: 30 задач и как решать их вслух
Большой практический гайд по собеседованию аналитика данных: SQL, Python, метрики, статистика, кейсы, дашборды и ответы, которые показывают ход мышления.