Как извлечь часть даты в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем это нужно аналитику
Работа с датами занимает 30-50% SQL-запросов аналитика. Нужно сгруппировать выручку по месяцам, сравнить конверсии по дням недели, проверить, какие часы пиковые, построить когортную таблицу по месяцу регистрации. Все эти задачи требуют извлечения части даты из timestamp.
Проблема в том, что в разных СУБД это делается по-разному — и на собеседованиях часто путают синтаксис. Postgres использует EXTRACT и DATE_TRUNC, MySQL — YEAR(), MONTH(), DATE_FORMAT, ClickHouse — свои toYear, toMonth. Плюс нюансы с днями недели (где начинается неделя) и таймзонами.
В статье — готовые примеры для всех популярных СУБД:
- Извлечение года / месяца / дня / часа
DATE_TRUNCдля группировки (округление до месяца, недели, часа)- Разница
EXTRACTvsDATE_TRUNC(число vs дата) - Работа с днями недели (Monday vs Sunday start)
- Группировка по 15-минутным интервалам
- Типичные грабли с таймзонами
Две основные функции
- EXTRACT / DATE_PART — вернуть число (год, месяц как число)
- DATE_TRUNC — округлить дату до периода (до месяца → первый день месяца)
1. EXTRACT (стандарт SQL)
SELECT
EXTRACT(YEAR FROM created_at) AS year,
EXTRACT(MONTH FROM created_at) AS month,
EXTRACT(DAY FROM created_at) AS day,
EXTRACT(HOUR FROM created_at) AS hour,
EXTRACT(MINUTE FROM created_at) AS minute,
EXTRACT(DOW FROM created_at) AS day_of_week,
EXTRACT(QUARTER FROM created_at) AS quarter
FROM orders;Постгрес DATE_PART (синоним)
SELECT DATE_PART('year', created_at) FROM orders;2. DATE_TRUNC
Округляет до периода. Возвращает дату, не число.
SELECT
DATE_TRUNC('year', created_at) AS year_start, -- 2026-01-01 00:00
DATE_TRUNC('month', created_at) AS month_start, -- 2026-04-01 00:00
DATE_TRUNC('week', created_at) AS week_start, -- 2026-04-20 (понедельник)
DATE_TRUNC('day', created_at) AS day_start, -- 2026-04-22 00:00
DATE_TRUNC('hour', created_at) AS hour_start
FROM orders;3. MySQL
SELECT
YEAR(created_at) AS year,
MONTH(created_at) AS month,
DAY(created_at) AS day,
HOUR(created_at) AS hour,
DAYOFWEEK(created_at) AS dow, -- 1=Sunday, 7=Saturday
WEEKDAY(created_at) AS weekday -- 0=Monday, 6=Sunday
FROM orders;
-- аналог DATE_TRUNC — DATE_FORMAT
SELECT DATE_FORMAT(created_at, '%Y-%m-01') AS month_start FROM orders;4. ClickHouse
SELECT
toYear(created_at),
toMonth(created_at),
toDayOfMonth(created_at),
toDayOfWeek(created_at),
toStartOfMonth(created_at), -- аналог DATE_TRUNC month
toStartOfWeek(created_at)
FROM orders;5. BigQuery
SELECT
EXTRACT(YEAR FROM created_at),
EXTRACT(MONTH FROM created_at),
DATE_TRUNC(created_at, MONTH),
DATE_TRUNC(created_at, WEEK(MONDAY))
FROM orders;6. Группировка по частям
По месяцам
SELECT DATE_TRUNC('month', created_at) AS month, COUNT(*)
FROM orders
GROUP BY 1
ORDER BY 1;По дню недели
SELECT EXTRACT(DOW FROM created_at) AS dow, COUNT(*)
FROM orders
GROUP BY 1
ORDER BY 1;По часам
SELECT EXTRACT(HOUR FROM created_at) AS hour, COUNT(*)
FROM orders
GROUP BY 1
ORDER BY 1;7. День недели: понедельник vs воскресенье
Разные СУБД считают по-разному:
- Postgres DOW: 0=воскресенье, 6=суббота
- Postgres ISODOW: 1=понедельник, 7=воскресенье
- MySQL DAYOFWEEK: 1=воскресенье
- MySQL WEEKDAY: 0=понедельник
Для чёткости используйте ISODOW в Postgres:
EXTRACT(ISODOW FROM created_at) -- 1-7 (Mon-Sun)8. Полезные трюки
Имя месяца
-- Postgres
SELECT TO_CHAR(created_at, 'Month') FROM orders;
-- MySQL
SELECT MONTHNAME(created_at) FROM orders;Номер недели года
EXTRACT(WEEK FROM created_at) -- 1-52Дней в месяце
-- Postgres
EXTRACT(DAY FROM
DATE_TRUNC('month', created_at) + INTERVAL '1 month - 1 day'
)9. Выходные / будни
SELECT
CASE
WHEN EXTRACT(DOW FROM created_at) IN (0, 6) THEN 'weekend'
ELSE 'weekday'
END AS day_type,
COUNT(*)
FROM orders
GROUP BY 1;10. DATE_TRUNC vs EXTRACT — когда что
| Задача | Выбор |
|---|---|
| Группировка «по месяцам» | DATE_TRUNC |
| Сравнение значения (это январь?) | EXTRACT |
| Показать «апрель 2026» | DATE_TRUNC + TO_CHAR |
| Сколько часов в events | EXTRACT |
Частые ошибки
Таймзоны
EXTRACT с timestamp без зоны может дать неожиданное значение. Всегда явно указывайте:
EXTRACT(HOUR FROM created_at AT TIME ZONE 'Europe/Moscow')Integer vs string
EXTRACT возвращает NUMERIC — не строку. Для форматирования:
TO_CHAR(created_at, 'YYYY-MM-DD')Неверная группировка
Если GROUP BY EXTRACT(MONTH) — не учитывает год. Январь 2025 и 2026 попадут в одну группу. Используйте DATE_TRUNC('month').
Связанные темы
FAQ
EXTRACT или DATE_PART?
Синонимы в Postgres. EXTRACT — стандарт SQL. DATE_PART — Postgres-специфично.
Как получить имя дня недели?
Postgres: TO_CHAR(d, 'Day'). MySQL: DAYNAME(d).
DATE_TRUNC или DATE_FORMAT?
DATE_TRUNC в Postgres. DATE_FORMAT в MySQL. Функционально похожи.
Как учесть таймзоны?
AT TIME ZONE 'zone' в Postgres перед EXTRACT.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.