SQL Studio / Разборы SQL / Почему BETWEEN пропускает последний день
Практическое решение: для столбца с датой и временем используйте полуоткрытый интервал — created_at >= '2026-09-01' и created_at < '2026-09-06'. Это включает любой момент 5 сентября и исключает начало следующего дня.
1. Ошибка: BETWEEN с датой без времени
Учебная таблица orders хранит created_at в формате YYYY-MM-DD HH:MM:SS. Заказ №205 создан 5 сентября в 15:15. Запрос ниже выглядит логично:
SELECT order_id, created_at
FROM orders
WHERE created_at BETWEEN '2026-09-01' AND '2026-09-05'
ORDER BY created_at, order_id;
Но он вернёт только четыре заказа: 201–204. BETWEEN включает обе границы, однако строка '2026-09-05 15:15:00' больше строки '2026-09-05'. Поэтому заказ №205 исключён. Проблема не в том, что BETWEEN исключает конец; проблема в значении самой верхней границы.
2. Надёжнее: от включительно, до не включительно
SELECT order_id, created_at
FROM orders
WHERE created_at >= '2026-09-01'
AND created_at < '2026-09-06'
ORDER BY created_at, order_id;
Теперь результат содержит пять заказов: 201–205. Если база хранит миллисекунды или микросекунды, ничего менять не требуется: весь последний день по-прежнему внутри диапазона.
| order_id | created_at | BETWEEN | Полуоткрытый |
|---|---|---|---|
| 205 | 2026-09-05 15:15 | Нет | Да |
| 206 | 2026-09-06 10:05 | Нет | Нет |
3. Контрольный запрос для отчёта
SELECT COUNT(*) AS orders_n,
MIN(created_at) AS first_at,
MAX(created_at) AS last_at
FROM orders
WHERE created_at >= '2026-09-01'
AND created_at < '2026-09-06';
На нашем наборе orders_n = 5, первая дата — 2026-09-01 11:20:00, последняя — 2026-09-05 15:15:00. Такая проверка помогает поймать пропущенные границы, прежде чем показатели попадут в отчёт.
4. Выбор всех заказов за месяц
SELECT order_id, created_at
FROM orders
WHERE created_at >= '2026-09-01'
AND created_at < '2026-10-01'
ORDER BY created_at, order_id;
Границы месяца задаём началом текущего и следующего месяцев. Не приходится учитывать, сколько в месяце дней и с какой точностью записано время.
Проверка полуоткрытых границ независимо от базы
В этом коротком примере первая полночь и последний момент 5 сентября включены, а ровно 00:00 6 сентября исключено. Сравнения работают для однородных ISO timestamp-строк SQLite; при смешанных часовых поясах нужно сначала привести значения к одной временной шкале.
WITH moments(ts) AS (
VALUES ('2026-09-01 00:00:00'),
('2026-09-05 23:59:59.999'),
('2026-09-06 00:00:00')
)
SELECT ts FROM moments
WHERE ts >= '2026-09-01' AND ts < '2026-09-06'
ORDER BY ts;5. Текстовая дата, DATETIME и часовой пояс
В нашей SQLite-базе все даты записаны однообразными ISO-строками, поэтому лексикографическое сравнение работает. В MySQL 8 столбцы DATETIME и TIMESTAMP имеют разные правила хранения и преобразования. Если отчёт считается по локальному дню, сначала определите нужный часовой пояс — фильтр сам это решение не примет.
Условие вида DATE(created_at) = '2026-09-05' иногда удобно, но функция над индексируемым столбцом может затруднить использование индекса. Диапазон напрямую по столбцу часто предпочтительнее.
Практика
Разберитесь с минимальной и максимальной датой заказа, затем попробуйте изменить границы в SQL-песочнице.
Первоисточник семантики оператора: SQLite — BETWEEN.
Для последовательной практики на MySQL 8 — курс «SQL для аналитика» на Stepik с итоговой работой и возможностью получить сертификат.