В реальных проектах часто требуется отобрать записи, которые были созданы или изменены за определённый период времени; Одним из самых популярных диапазонов является «за последние 45 дней». В этой статье мы разберём, как сформировать такие запросы в разных СУБД (MySQL, PostgreSQL, Microsoft SQL Server, Oracle) и какие нюансы следует учитывать для корректной работы и высокой производительности.
Общие принципы работы с датой в SQL
- Типы данных –
DATE,DATETIME,TIMESTAMP,TIME. Выбирайте тип, соответствующий точности, которую вам нужен. - Функции получения текущей даты/времени –
NOW,CURRENT_DATE,GETDATE,SYSTIMESTAMPи т.д. - Операторы интервала –
INTERVAL(MySQL, PostgreSQL),DATEADD(SQL Server),NUMTODSINTERVAL(Oracle). - Функции сравнения –
DATEDIFF,DATE_PART,EXTRACTи др.
Для получения диапазона «за последние 45 дней» нам нужны две даты:
- Текущая дата/время (верхняя граница).
- Дата, отстоящая от текущей на 45 дней назад (нижняя граница).
Далее мы будем использовать эти две границы в условии WHERE с помощью разных синтаксисов.
MySQL / MariaDB
Самый простой вариант
SELECT *
FROM orders
WHERE order_date >= CURDATE ー INTERVAL 45 DAY
AND order_date < CURDATE + INTERVAL 1 DAY; -- включаем сегодняшние записи
Пояснения:
CURDATEвозвращает текущую дату без времени.- Выражение
CURDATE ー INTERVAL 45 DAYдаёт дату 45 дней назад. - В условии
>=и<мы получаем полные 45 дней, включая сегодня.
С учётом времени (DATETIME / TIMESTAMP)
SELECT *
FROM logs
WHERE log_timestamp BETWEEN NOW ⎯ INTERVAL 45 DAY AND NOW;
Функция NOW возвращает текущий момент (дата + время), поэтому диапазон будет точным до секунды.
Использование DATE_SUB (альтернатива)
SELECT *
FROM events
WHERE event_date >= DATE_SUB(CURDATE, INTERVAL 45 DAY);
Индексирование
Если колонка order_date (или другая дата) имеет индекс, запрос будет использовать диапазонный поиск, что обеспечивает быстрый отклик даже на больших таблицах.
PostgreSQL
Функция CURRENT_DATE и оператор INTERVAL
SELECT *
FROM sales
WHERE sale_date >= (CURRENT_DATE ー INTERVAL '45 days')
AND sale_date < (CURRENT_DATE + INTERVAL '1 day');
Точнее – с NOW (TIMESTAMP)
SELECT *
FROM activity
WHERE activity_ts BETWEEN NOW ー INTERVAL '45 days' AND NOW;
Функция AGE и DATE_TRUNC
Если нужно сравнить только по дате без времени, удобно обрезать метку времени:
SELECT *
FROM visits
WHERE DATE_TRUNC('day', visit_ts)
BETWEEN (CURRENT_DATE ⎯ INTERVAL '45 days')
AND CURRENT_DATE;
Индексы и BRIN
Для огромных таблиц с датой удобно использовать BRIN-индекс:
CREATE INDEX idx_visits_ts_brin
ON visits USING BRIN (visit_ts);
Он ускорит диапазонные запросы, такие как «за последние 45 дней».
Microsoft SQL Server (T‑SQL)
Функция GETDATE + DATEADD
SELECT *
FROM dbo.Purchases
WHERE PurchaseDate >= DATEADD(day, -45, CAST(GETDATE AS DATE))
AND PurchaseDate < CAST(GETDATE AS DATE) + 1; -- включаем сегодня
Если колонка хранит datetime2 (время тоже)
SELECT *
FROM dbo.Events
WHERE EventDateTime BETWEEN DATEADD(day, -45, GETDATE) AND GETDATE;
Использование DATEDIFF (не рекомендуется для индексов)
Вариант, который часто встречается в примерах, но приводит к сканированию таблицы:
-- НЕ ПОЛУЧИТЕ ИНДЕКС! Лучше использовать диапазон, как выше.
SELECT *
FROM dbo.Orders
WHERE DATEDIFF(day, OrderDate, GETDATE) <= 45;
Индексирование
Создайте обычный B‑tree индекс по колонке даты:
CREATE INDEX idx_orders_date ON dbo.Orders (OrderDate);
Тогда запрос с диапазоном (>= … AND < …) будет использовать seek‑операцию.
Oracle
Текущая дата – SYSDATE
SELECT *
FROM employees
WHERE hire_date BETWEEN SYSDATE ー 45 AND SYSDATE;
В Oracle интервал в днях задаётся просто числом, поэтому SYSDATE ー 45 уже даёт дату 45 дней назад.
Точнее – с INTERVAL и TIMESTAMP
SELECT *
FROM logs
WHERE log_ts BETWEEN SYSTIMESTAMP ⎯ INTERVAL '45' DAY
AND SYSTIMESTAMP;
Приведение к дате без времени
Если колонка хранит TIMESTAMP, а нам нужны только даты, используем TRUNC:
SELECT *
FROM visits
WHERE TRUNC(visit_ts) BETWEEN TRUNC(SYSDATE) ー 45 AND TRUNC(SYSDATE);
Индексы
Создайте обычный B‑tree индекс по колонке даты:
CREATE INDEX idx_visits_ts ON visits (visit_ts);
Если запрос часто использует TRUNC(visit_ts), лучше создать функциональный индекс:
CREATE INDEX idx_visits_ts_trunc ON visits (TRUNC(visit_ts));
Общие рекомендации по производительности
- Используйте диапазонные условия (
col BETWEEN … AND …илиcol >= … AND col < …). Они позволяют СУБД применять индексы. - Избегайте функций над колонкой (например,
DATEDIFF(col, …)в SQL Server). Такие конструкции заставляют СУБД выполнять полное сканирование. - Проверяйте план выполнения (
EXPLAIN,EXPLAIN ANALYZE,SET SHOWPLAN_ALL ONи т.п.) чтобы убедиться, что используется индекс. - Если требуется только дата без времени, храните её в типе
DATEили создавайте функциональный индекс поTRUNC(col)/CAST(col AS DATE). - Периодическое обновление статистики помогает оптимизатору выбирать правильный план.
Полный набор примеров в виде таблицы
| СУБД | Текущая дата/время | Запрос (дата без времени) | Запрос (время учитывается) |
|---|---|---|---|
| MySQL | CURDATE / NOW | WHERE dt >= CURDATE ー INTERVAL 45 DAY | WHERE dt BETWEEN NOW ー INTERVAL 45 DAY AND NOW |
| PostgreSQL | CURRENT_DATE / NOW | WHERE dt >= (CURRENT_DATE ⎯ INTERVAL '45 days') | WHERE dt BETWEEN NOW ー INTERVAL '45 days' AND NOW |
| SQL Server | GETDATE | WHERE dt >= DATEADD(day, -45, CAST(GETDATE AS DATE)) | WHERE dt BETWEEN DATEADD(day, -45, GETDATE) AND GETDATE |
| Oracle | SYSDATE / SYSTIMESTAMP | WHERE dt BETWEEN SYSDATE ー 45 AND SYSDATE | WHERE dt BETWEEN SYSTIMESTAMP ー INTERVAL '45' DAY AND SYSTIMESTAMP |
Запросы «за последние 45 дней» одинаково просты во всех популярных СУБД, но детали синтаксиса отличаються. Главное – помнить о двух вещах:
- Определить правильный верхний и нижний предел с помощью функций
NOW,CURRENT_DATE,GETDATEи операторовINTERVAL/DATEADD. - Сделать так, чтобы условие сравнения использовало колонку «как есть», а не её результат функции – тогда СУБД сможет задействовать индекс и запрос выполнится быстро.
Следуя этим рекомендациям, вы сможете без проблем получать нужные данные за любые динамические периоды, в т.ч. за последние 45 дней.