Как извлечь часть даты в 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 для группировки (округление до месяца, недели, часа)
  • Разница EXTRACT vs DATE_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+ вопросами для собесов.