INTERVAL в SQL: шпаргалка

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Что такое INTERVAL

INTERVAL — тип данных в SQL для хранения временных промежутков (дни, месяцы, часы). Позволяет делать арифметику с датами.

INTERVAL '1 day'
INTERVAL '7 days'
INTERVAL '2 hours'
INTERVAL '3 months'
INTERVAL '1 year 6 months'

Синтаксис по СУБД

PostgreSQL

CURRENT_DATE + INTERVAL '7 days'
NOW() - INTERVAL '1 hour'

MySQL

DATE_ADD(CURDATE(), INTERVAL 7 DAY)
CURDATE() - INTERVAL 7 DAY

ClickHouse

today() + INTERVAL 7 DAY
now() - INTERVAL 1 HOUR

BigQuery / Snowflake

-- BigQuery
DATE_ADD(CURRENT_DATE(), INTERVAL 7 DAY)

-- Snowflake
DATEADD(DAY, 7, CURRENT_DATE())

1. Прибавить/отнять к дате

-- Postgres
SELECT NOW() + INTERVAL '30 days';
SELECT CURRENT_DATE - INTERVAL '1 month';
SELECT '2026-01-01'::DATE + INTERVAL '1 year';

-- MySQL
SELECT CURDATE() + INTERVAL 7 DAY;
SELECT NOW() - INTERVAL 1 HOUR;

2. Заказы за последние 7 дней

-- Postgres
SELECT * FROM orders
WHERE created_at >= NOW() - INTERVAL '7 days';

-- MySQL
SELECT * FROM orders
WHERE created_at >= NOW() - INTERVAL 7 DAY;

3. Заказы в текущем месяце

SELECT * FROM orders
WHERE created_at >= DATE_TRUNC('month', CURRENT_DATE)
  AND created_at <  DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month';

4. Предыдущий месяц

-- Postgres
SELECT * FROM orders
WHERE created_at >= DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 month'
  AND created_at <  DATE_TRUNC('month', CURRENT_DATE);

5. Пользователи, зарегистрировавшиеся сегодня

SELECT * FROM users
WHERE created_at >= CURRENT_DATE
  AND created_at <  CURRENT_DATE + INTERVAL '1 day';

6. Разность между датами

Postgres

-- разница в INTERVAL
SELECT MAX(created_at) - MIN(created_at) AS span
FROM orders WHERE user_id = 1;

-- разница в днях (int)
SELECT (MAX(created_at) - MIN(created_at))::INT AS days
FROM orders;

-- альтернатива через AGE
SELECT EXTRACT(DAY FROM AGE(max_date, min_date)) AS days;

MySQL

SELECT DATEDIFF(max_date, min_date) AS days;
SELECT TIMESTAMPDIFF(HOUR, start_at, end_at) AS hours;
SELECT TIMESTAMPDIFF(MONTH, birth_date, CURDATE()) AS months;

ClickHouse

SELECT dateDiff('day', start_at, end_at) AS days;
SELECT dateDiff('hour', start_at, end_at) AS hours;

7. Комбинированные интервалы

-- Postgres
SELECT NOW() + INTERVAL '1 year 6 months 7 days';

-- MySQL (только один компонент за раз)
SELECT DATE_ADD(DATE_ADD(NOW(), INTERVAL 1 YEAR), INTERVAL 6 MONTH);

8. Время между заказами пользователя

-- Postgres
SELECT
    user_id,
    order_id,
    created_at - LAG(created_at) OVER (
        PARTITION BY user_id ORDER BY created_at
    ) AS time_since_prev_order
FROM orders;

9. Пользователи, неактивные 30+ дней

SELECT user_id, MAX(event_at) AS last_event
FROM events
GROUP BY user_id
HAVING MAX(event_at) < NOW() - INTERVAL '30 days';

10. Календарь дней

-- Postgres — generate_series
SELECT day::DATE
FROM generate_series(
    DATE '2026-01-01',
    DATE '2026-12-31',
    INTERVAL '1 day'
) AS day;

11. Группировка по интервалам

По неделе

SELECT DATE_TRUNC('week', created_at) AS week, COUNT(*)
FROM orders GROUP BY 1;

По 15-минутным интервалам

-- Postgres
SELECT
    DATE_TRUNC('hour', created_at)
    + FLOOR(EXTRACT(MINUTE FROM created_at) / 15) * INTERVAL '15 minutes' AS bucket,
    COUNT(*)
FROM orders
GROUP BY 1;

12. Арифметика с интервалами

-- умножение
SELECT INTERVAL '1 day' * 7;        -- 7 дней
SELECT '2026-01-01'::DATE + (n * INTERVAL '1 month')
FROM generate_series(0, 11) n;       -- месяцы 2026

-- сумма
SELECT INTERVAL '1 hour' + INTERVAL '30 minutes';  -- 01:30:00

Что такое таймзоны

Дата без таймзоны (TIMESTAMP) и с таймзоной (TIMESTAMPTZ) ведут себя по-разному:

-- Postgres
SELECT NOW();                                          -- 2026-04-21 10:30:00+03
SELECT NOW() AT TIME ZONE 'UTC';                       -- 07:30:00 (без зоны)
SELECT '2026-04-21 10:00'::TIMESTAMPTZ AT TIME ZONE 'Europe/Moscow';

Если сервер в UTC, а анализ в МСК — обязательно конвертируйте перед группировкой по дням.

Частые ошибки

Ошибка 1. BETWEEN исключает часть времени

-- теряем события 2026-01-31 23:00+
WHERE created_at BETWEEN '2026-01-01' AND '2026-01-31'

-- правильно
WHERE created_at >= '2026-01-01'
  AND created_at <  '2026-02-01'

Ошибка 2. INTERVAL '1 month' ≠ 30 days

Месяц — переменной длины. Добавление INTERVAL '1 month' к 31 января даст 28 февраля (или 29).

Ошибка 3. Смешивание TIMESTAMP и TIMESTAMPTZ

Часто приводит к сюрпризам с таймзонами. Выбирайте один тип и придерживайтесь.

Ошибка 4. NULL в арифметике

-- если last_login IS NULL, вернётся NULL
last_login < NOW() - INTERVAL '30 days'

-- учитывайте NULL отдельно
last_login IS NULL OR last_login < NOW() - INTERVAL '30 days'

Ошибка 5. «Неделя» начинается по-разному

Postgres ISO: понедельник. MySQL: воскресенье / настраивается. Уточняйте.

Связанные темы

FAQ

Можно ли INTERVAL в арифметике?

Да: INTERVAL × number, INTERVAL + INTERVAL. Умножение интервалов между собой — нет.

Как получить разницу в минутах?

Postgres: EXTRACT(EPOCH FROM (a - b)) / 60. MySQL: TIMESTAMPDIFF(MINUTE, b, a). ClickHouse: dateDiff('minute', b, a).

INTERVAL и DATE_ADD — что использовать?

В Postgres — INTERVAL (proпер тип). В MySQL — оба работают, INTERVAL обычно короче.

Как посчитать возраст в годах?

Postgres: EXTRACT(YEAR FROM AGE(birth_date)). MySQL: TIMESTAMPDIFF(YEAR, birth_date, CURDATE()).


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.