SQLEXTRACTdatetutorial

Что такое EXTRACT в SQL?

EXTRACT — это «достань кусок из даты»: год, месяц, день, час, день недели. Простыми словами: как сгруппировать данные по году/месяцу, отфильтровать по дню недели, посчитать секунды через EPOCH. Сравнение с DATE_TRUNC и отличия PostgreSQL и MySQL.

12 мин чтенияСправочникSQL · EXTRACT · date · tutorial

EXTRACT — это способ достать из даты или времени отдельную часть: год, месяц, день, час, минуту, день недели, квартал и многое другое.

Представь, что дата — это не одно простое значение, а аккуратно упакованная коробка. Внутри лежат сразу несколько деталей:

  • год;
  • месяц;
  • день;
  • часы;
  • минуты;
  • секунды;
  • день недели;
  • номер недели;
  • квартал.

EXTRACT открывает эту коробку и достаёт ровно ту деталь, которая нужна в запросе.

Например, из даты 2026-03-15 можно достать:

  • год: 2026;
  • месяц: 3;
  • день: 15.

Это особенно полезно в аналитике: посчитать регистрации по годам, найти заказы по месяцам, отобрать события только по понедельникам или измерить разницу между двумя событиями в секундах.

Зачем нужен EXTRACT

В реальных таблицах даты почти всегда хранятся целиком. Например, в колонке created_at может лежать значение:

TIMESTAMP '2026-03-15 10:30:00'

Для базы данных это одно значение. Но человеку часто нужна не вся дата, а отдельный смысловой кусочек.

Например:

  • для отчёта по годам нужен только год;
  • для отчёта по месяцам нужен месяц;
  • для фильтра «только рабочие дни» нужен день недели;
  • для анализа активности по часам нужен час;
  • для расчёта длительности нужны секунды.

Вот здесь и появляется EXTRACT.

Он отвечает на вопрос:

«Достань из этой даты конкретную часть и верни её числом».

Базовый синтаксис

Общий вид такой:

EXTRACT(field FROM date_expression)

Где:

  • field — какую часть даты нужно достать;
  • date_expression — дата, время, timestamp или интервал.

Примеры:

SELECT EXTRACT(YEAR FROM TIMESTAMP '2026-03-15 10:30:00');

SELECT EXTRACT(MONTH FROM DATE '2026-03-15');

SELECT EXTRACT(DAY FROM DATE '2026-03-15');

SELECT EXTRACT(HOUR FROM TIMESTAMP '2026-03-15 10:30:00');

Результат будет таким:

expression result
YEAR 2026
MONTH 3
DAY 15
HOUR 10

То есть EXTRACT не возвращает новую дату. Он возвращает число.

Самые полезные поля EXTRACT

Вот поля, которые встречаются чаще всего.

Поле Что возвращает
YEAR Год
MONTH Месяц от 1 до 12
DAY День месяца от 1 до 31
HOUR Час от 0 до 23
MINUTE Минуту от 0 до 59
SECOND Секунду, иногда с дробной частью
DOW День недели: 0 — воскресенье, 6 — суббота
ISODOW День недели по ISO: 1 — понедельник, 7 — воскресенье
DOY День года от 1 до 366
WEEK Номер ISO-недели от 1 до 53
QUARTER Квартал от 1 до 4
EPOCH Количество секунд

Для новичка самые важные: YEAR, MONTH, DAY, HOUR, ISODOW и EPOCH.

С ними ты уже сможешь решать большую часть обычных задач с датами.

Простой пример

Допустим, есть дата заказа:

SELECT
  EXTRACT(YEAR FROM TIMESTAMP '2026-03-15 10:30:00') AS order_year,
  EXTRACT(MONTH FROM TIMESTAMP '2026-03-15 10:30:00') AS order_month,
  EXTRACT(DAY FROM TIMESTAMP '2026-03-15 10:30:00') AS order_day;

Результат:

order_year order_month order_day
2026 3 15

База взяла один timestamp и разложила его на понятные части.

Пример с таблицей orders

Пусть есть таблица orders:

id created_at amount
1 2026-01-15 10:00:00 100
2 2026-02-03 14:00:00 250
3 2026-02-20 11:30:00 80
4 2026-03-05 09:00:00 500

Нужно посчитать количество заказов и сумму продаж по месяцам.

SELECT
  EXTRACT(MONTH FROM created_at) AS month,
  COUNT(*) AS orders_count,
  SUM(amount) AS total_amount
FROM orders
GROUP BY 1
ORDER BY 1;

Результат:

month orders_count total_amount
1 1 100
2 2 330
3 1 500

Здесь EXTRACT(MONTH FROM created_at) достал номер месяца из каждого заказа. Потом GROUP BY собрал заказы с одинаковым месяцем в группы.

Группировка по году и месяцу

Одна частая ошибка новичков — группировать только по месяцу и забывать про год.

Например:

SELECT
  EXTRACT(MONTH FROM created_at) AS month,
  COUNT(*) AS orders_count
FROM orders
GROUP BY 1
ORDER BY 1;

На первый взгляд всё хорошо. Но если в таблице есть данные за несколько лет, январь 2025 года и январь 2026 года попадут в одну группу.

Чаще правильно писать так:

SELECT
  EXTRACT(YEAR FROM created_at) AS year,
  EXTRACT(MONTH FROM created_at) AS month,
  COUNT(*) AS orders_count
FROM orders
GROUP BY 1, 2
ORDER BY 1, 2;

Теперь январь разных лет не смешается.

Результат будет выглядеть примерно так:

year month orders_count
2025 12 18
2026 1 24
2026 2 31
2026 3 19

Это уже нормальная основа для отчёта.

EXTRACT или DATE_TRUNC

Для отчётов по периодам в SQL часто используют не только EXTRACT, но и DATE_TRUNC.

Разница простая:

  • EXTRACT достаёт часть даты и возвращает число;
  • DATE_TRUNC обрезает дату до начала периода и возвращает дату или timestamp.

Например:

SELECT
  EXTRACT(YEAR FROM created_at) AS year,
  EXTRACT(MONTH FROM created_at) AS month,
  COUNT(*) AS users_count
FROM users
WHERE created_at >= TIMESTAMP '2026-01-01 00:00:00'
  AND created_at < TIMESTAMP '2027-01-01 00:00:00'
GROUP BY 1, 2
ORDER BY 1, 2;

А вот вариант через DATE_TRUNC:

SELECT
  DATE_TRUNC('month', created_at) AS month_start,
  COUNT(*) AS users_count
FROM users
WHERE created_at >= TIMESTAMP '2026-01-01 00:00:00'
  AND created_at < TIMESTAMP '2027-01-01 00:00:00'
GROUP BY 1
ORDER BY 1;

Если created_at равен 2026-03-15 10:30:00, то:

SELECT DATE_TRUNC('month', TIMESTAMP '2026-03-15 10:30:00');

Вернёт начало месяца:

2026-03-01 00:00:00

Когда что выбирать?

EXTRACT удобно использовать, когда нужны отдельные числовые колонки:

year month users_count
2026 1 120
2026 2 155

DATE_TRUNC удобно использовать, когда нужен один столбец периода:

month_start users_count
2026-01-01 00:00:00 120
2026-02-01 00:00:00 155

Для графиков, BI-отчётов и временных рядов DATE_TRUNC часто удобнее. Для учебных задач и отдельных компонентов даты EXTRACT выглядит нагляднее.

Фильтр по месяцу

Допустим, нужно найти все события, которые произошли в марте любого года.

SELECT *
FROM events
WHERE EXTRACT(MONTH FROM occurred_at) = 3;

Такой запрос найдёт март и в 2024, и в 2025, и в 2026 году.

Это важно понимать: здесь мы фильтруем именно номер месяца, а не конкретный март конкретного года.

Если нужен март 2026 года, лучше писать диапазон:

SELECT *
FROM events
WHERE occurred_at >= TIMESTAMP '2026-03-01 00:00:00'
  AND occurred_at < TIMESTAMP '2026-04-01 00:00:00';

Так запрос точнее: он берёт только даты с начала марта 2026 до начала апреля 2026.

Почему диапазон часто лучше EXTRACT в WHERE

На маленьких таблицах разницы можно не заметить. Но на больших таблицах это очень важно.

Допустим, на колонке occurred_at есть обычный индекс. Такой запрос часто может использовать индекс:

SELECT *
FROM events
WHERE occurred_at >= TIMESTAMP '2026-03-01 00:00:00'
  AND occurred_at < TIMESTAMP '2026-04-01 00:00:00';

А такой запрос может помешать обычному индексу:

SELECT *
FROM events
WHERE EXTRACT(MONTH FROM occurred_at) = 3;

Почему?

Потому что база должна взять значение occurred_at, применить к нему функцию EXTRACT, получить месяц и только потом сравнить его с 3.

То есть индекс по исходной дате уже не так полезен.

Простое правило:

Если фильтруешь конкретный период, используй диапазон дат.

Хорошо:

SELECT *
FROM orders
WHERE created_at >= TIMESTAMP '2026-01-01 00:00:00'
  AND created_at < TIMESTAMP '2026-02-01 00:00:00';

Менее удачно для больших таблиц:

SELECT *
FROM orders
WHERE EXTRACT(YEAR FROM created_at) = 2026
  AND EXTRACT(MONTH FROM created_at) = 1;

EXTRACT в фильтре не запрещён. Просто для производительности диапазонная форма часто лучше.

Фильтр по дню недели

Одна из самых приятных задач для EXTRACT — работа с днями недели.

Например, нужно выбрать события, которые случились в понедельник:

SELECT *
FROM events
WHERE EXTRACT(ISODOW FROM occurred_at) = 1;

Почему ISODOW, а не DOW?

Потому что у них разная нумерация.

Поле Как нумеруются дни
DOW 0 — воскресенье, 1 — понедельник, ..., 6 — суббота
ISODOW 1 — понедельник, 2 — вторник, ..., 7 — воскресенье

В обычной жизни в России и Европе неделя чаще начинается с понедельника. Поэтому для понятной логики обычно удобнее ISODOW.

Например, выбрать события только в будни:

SELECT *
FROM events
WHERE EXTRACT(ISODOW FROM occurred_at) BETWEEN 1 AND 5;

А выбрать только выходные:

SELECT *
FROM events
WHERE EXTRACT(ISODOW FROM occurred_at) IN (6, 7);

Фильтр по рабочему времени

Можно совместить день недели и час.

Например, нужно найти события, которые произошли в рабочее время: с понедельника по пятницу, с 9:00 до 17:59.

SELECT *
FROM events
WHERE EXTRACT(ISODOW FROM occurred_at) BETWEEN 1 AND 5
  AND EXTRACT(HOUR FROM occurred_at) BETWEEN 9 AND 17;

Здесь логика такая:

  • ISODOW BETWEEN 1 AND 5 — только понедельник, вторник, среда, четверг и пятница;
  • HOUR BETWEEN 9 AND 17 — часы от 9 до 17 включительно.

То есть время 17:45 подойдёт, потому что час равен 17.

А время 18:00 уже не подойдёт, потому что час равен 18.

EXTRACT и EPOCH

EPOCH — особое поле. Оно работает с секундами.

В PostgreSQL EXTRACT(EPOCH FROM ...) может использоваться в двух популярных случаях:

  1. получить Unix timestamp;
  2. посчитать длительность интервала в секундах.

Unix timestamp — это количество секунд с начала эпохи Unix: 1970-01-01 00:00:00 UTC.

Пример:

SELECT EXTRACT(EPOCH FROM TIMESTAMP '2026-03-15 12:00:00');

Результатом будет количество секунд.

Но на практике чаще EPOCH используют для интервалов.

Например, нужно узнать, сколько секунд между двумя timestamp-значениями:

SELECT
  EXTRACT(
    EPOCH FROM (
      TIMESTAMP '2026-03-15 10:30:00'
      - TIMESTAMP '2026-03-15 09:00:00'
    )
  ) AS seconds_diff;

Результат:

seconds_diff
5400

5400 секунд — это полтора часа.

Перевод секунд в минуты, часы и дни

Раз EPOCH возвращает секунды, их можно делить.

Например, разница в минутах:

SELECT
  EXTRACT(
    EPOCH FROM (
      TIMESTAMP '2026-03-15 10:30:00'
      - TIMESTAMP '2026-03-15 09:00:00'
    )
  ) / 60 AS minutes_diff;

Результат:

minutes_diff
90

Разница в часах:

SELECT
  EXTRACT(
    EPOCH FROM (
      TIMESTAMP '2026-03-15 10:30:00'
      - TIMESTAMP '2026-03-15 09:00:00'
    )
  ) / 3600 AS hours_diff;

Результат:

hours_diff
1.5

Разница в днях:

SELECT
  name,
  EXTRACT(EPOCH FROM (NOW() - created_at)) / 86400 AS days_since_signup
FROM users;

Здесь 86400 — количество секунд в сутках.

EPOCH может вернуть дробное число

Если timestamp содержит микросекунды, результат тоже может быть дробным.

Например:

SELECT EXTRACT(EPOCH FROM TIMESTAMP '2026-03-15 12:00:00.123456');

Результат будет с дробной частью.

Если нужен целый Unix timestamp, можно привести результат к целому типу:

SELECT EXTRACT(EPOCH FROM TIMESTAMP '2026-03-15 12:00:00')::BIGINT;

В PostgreSQL ::BIGINT — это приведение типа.

Часовые пояса: важный момент

С датами и временем всегда есть тонкое место — часовой пояс.

Если колонка хранит TIMESTAMPTZ, то EXTRACT учитывает часовой пояс текущей сессии.

Например, один и тот же момент времени может быть:

  • 09:00 в UTC;
  • 12:00 в Москве;
  • 16:00 во Вьетнаме.

Если ты достаёшь час через EXTRACT(HOUR FROM ...), важно понимать: час в какой зоне ты хочешь получить?

В PostgreSQL можно явно указать часовой пояс через AT TIME ZONE.

Например, достать час в московском времени:

SELECT EXTRACT(HOUR FROM tstz AT TIME ZONE 'Europe/Moscow')
FROM events;

А если нужно получить час во вьетнамском времени:

SELECT EXTRACT(HOUR FROM tstz AT TIME ZONE 'Asia/Ho_Chi_Minh')
FROM events;

Главная мысль:

Если отчёт зависит от локального времени пользователя, не забывай про часовой пояс.

Иначе можно получить странные результаты: например, пользователь сделал заказ вечером, а в отчёте он попал в другой день, потому что база считала время в UTC.

Группировка по кварталам

EXTRACT удобно использовать и для кварталов.

SELECT
  EXTRACT(YEAR FROM created_at) AS year,
  EXTRACT(QUARTER FROM created_at) AS quarter,
  SUM(amount) AS total_amount
FROM orders
GROUP BY 1, 2
ORDER BY 1, 2;

Результат:

year quarter total_amount
2026 1 930
2026 2 1240
2026 3 880
2026 4 1510

И снова важное правило: квартал лучше группировать вместе с годом.

Плохо:

SELECT
  EXTRACT(QUARTER FROM created_at) AS quarter,
  SUM(amount) AS total_amount
FROM orders
GROUP BY 1
ORDER BY 1;

Так первый квартал 2025 года и первый квартал 2026 года попадут в одну группу.

Лучше:

SELECT
  EXTRACT(YEAR FROM created_at) AS year,
  EXTRACT(QUARTER FROM created_at) AS quarter,
  SUM(amount) AS total_amount
FROM orders
GROUP BY 1, 2
ORDER BY 1, 2;

День года и номер недели

Иногда нужны менее очевидные части даты.

Например, день года:

SELECT EXTRACT(DOY FROM DATE '2026-03-15') AS day_of_year;

DOY показывает номер дня внутри года: от 1 до 365 или 366.

Ещё есть номер недели:

SELECT EXTRACT(WEEK FROM DATE '2026-03-15') AS week_number;

В PostgreSQL WEEK возвращает номер ISO-недели.

С неделями надо быть внимательным: в разных базах данных правила могут отличаться. Особенно если ты переносишь запрос из PostgreSQL в MySQL или наоборот.

Отличия в MySQL

В MySQL тоже есть EXTRACT, но для многих задач чаще используют короткие функции.

Например:

SELECT YEAR('2026-03-15');

SELECT MONTH('2026-03-15');

SELECT DAY('2026-03-15');

SELECT HOUR('2026-03-15 10:30:00');

Для Unix timestamp в MySQL используют UNIX_TIMESTAMP.

SELECT UNIX_TIMESTAMP('2026-03-15 12:00:00');

Для дня недели есть DAYOFWEEK.

SELECT DAYOFWEEK('2026-03-15');

В MySQL у DAYOFWEEK своя нумерация: 1 — воскресенье, 2 — понедельник, и так далее.

Поэтому при переносе логики между PostgreSQL и MySQL всегда проверяй нумерацию дней недели. Это маленькая деталь, которая легко ломает отчёты.

Отличия в SQLite

В SQLite нет EXTRACT в привычном виде. Там обычно используют STRFTIME.

SELECT STRFTIME('%Y', '2026-03-15');

SELECT STRFTIME('%m', '2026-03-15');

SELECT STRFTIME('%d', '2026-03-15');

SELECT STRFTIME('%w', '2026-03-15');

Особенность STRFTIME: он часто возвращает текст, а не число.

Например, месяц может вернуться как строка '03', а не число 3.

Если нужно число, можно привести тип:

SELECT CAST(STRFTIME('%m', '2026-03-15') AS INTEGER);

Отличия в SQL Server

В SQL Server для похожих задач используют DATEPART.

SELECT DATEPART(year, '2026-03-15');

SELECT DATEPART(month, '2026-03-15');

SELECT DATEPART(day, '2026-03-15');

SELECT DATEPART(hour, '2026-03-15 10:30:00');

То есть идея та же самая: достать отдельную часть даты. Просто имя функции другое.

Частые ошибки новичков

Ошибка 1. Забыть год при группировке по месяцу

Проблемный вариант:

SELECT
  EXTRACT(MONTH FROM created_at) AS month,
  COUNT(*) AS orders_count
FROM orders
GROUP BY 1
ORDER BY 1;

Если в таблице несколько лет, январь разных лет смешается.

Правильнее:

SELECT
  EXTRACT(YEAR FROM created_at) AS year,
  EXTRACT(MONTH FROM created_at) AS month,
  COUNT(*) AS orders_count
FROM orders
GROUP BY 1, 2
ORDER BY 1, 2;

Ошибка 2. Использовать DOW вместо ISODOW

DOW начинается с воскресенья:

value day
0 Sunday
1 Monday
6 Saturday

ISODOW начинается с понедельника:

value day
1 Monday
2 Tuesday
7 Sunday

Если тебе нужна привычная неделя с понедельника по воскресенье, обычно выбирай ISODOW.

SELECT *
FROM events
WHERE EXTRACT(ISODOW FROM occurred_at) BETWEEN 1 AND 5;

Ошибка 3. Фильтровать период через EXTRACT вместо диапазона

Работает, но может быть медленнее:

SELECT *
FROM orders
WHERE EXTRACT(YEAR FROM created_at) = 2026
  AND EXTRACT(MONTH FROM created_at) = 3;

Лучше для конкретного месяца:

SELECT *
FROM orders
WHERE created_at >= TIMESTAMP '2026-03-01 00:00:00'
  AND created_at < TIMESTAMP '2026-04-01 00:00:00';

Так базе проще использовать обычный индекс по created_at.

Ошибка 4. Не учитывать часовой пояс

Если бизнес думает в локальном времени, а данные хранятся в UTC, отчёт может съехать.

Например, заказ был сделан поздно вечером по местному времени, но в UTC это уже другой день или другой час.

Используй явное преобразование:

SELECT
  EXTRACT(HOUR FROM created_at AT TIME ZONE 'Europe/Moscow') AS local_hour,
  COUNT(*) AS orders_count
FROM orders
GROUP BY 1
ORDER BY 1;

Ошибка 5. Ждать от EPOCH целое число

EPOCH может вернуть значение с дробной частью.

Если нужен целый результат:

SELECT EXTRACT(EPOCH FROM NOW())::BIGINT;

Ошибка 6. Считать недели одинаковыми во всех базах

В PostgreSQL EXTRACT(WEEK FROM date) работает с ISO-неделями.

В MySQL у WEEK(date) есть разные режимы. Они могут влиять на то, с какого дня начинается неделя и как считается первая неделя года.

Если делаешь отчёт по неделям, обязательно проверь правила конкретной СУБД.

Когда EXTRACT — хороший выбор

EXTRACT хорошо подходит, когда тебе нужно:

  • вывести год, месяц или день отдельной колонкой;
  • сгруппировать данные по году, месяцу или кварталу;
  • отфильтровать события по дню недели;
  • понять, в какой час пользователи чаще совершают действия;
  • посчитать длительность в секундах через EPOCH;
  • разобрать timestamp на отдельные части для отчёта.

Например, отчёт по активности пользователей по часам:

SELECT
  EXTRACT(HOUR FROM created_at) AS hour,
  COUNT(*) AS events_count
FROM events
GROUP BY 1
ORDER BY 1;

Результат:

hour events_count
0 12
1 8
2 5
9 44
10 61
11 73

Так можно быстро увидеть, когда пользователи наиболее активны.

Когда лучше не использовать EXTRACT

EXTRACT не всегда лучший инструмент.

Если тебе нужно отобрать конкретный период, например март 2026 года, чаще лучше использовать диапазон дат:

SELECT *
FROM events
WHERE occurred_at >= TIMESTAMP '2026-03-01 00:00:00'
  AND occurred_at < TIMESTAMP '2026-04-01 00:00:00';

Если тебе нужен отчёт по месяцам как временной ряд, часто удобнее DATE_TRUNC:

SELECT
  DATE_TRUNC('month', created_at) AS month_start,
  COUNT(*) AS events_count
FROM events
GROUP BY 1
ORDER BY 1;

А если тебе нужны именно отдельные числа — год, месяц, час, день недели — тогда EXTRACT подходит отлично.

Главное из статьи

EXTRACT — это функция для извлечения отдельной части из даты, времени, timestamp или интервала.

Базовый синтаксис:

EXTRACT(field FROM date_expression)

Самые частые поля:

Поле Зачем нужно
YEAR Достать год
MONTH Достать месяц
DAY Достать день месяца
HOUR Достать час
ISODOW Достать день недели с понедельника
QUARTER Достать квартал
EPOCH Получить секунды

Главные правила:

  • EXTRACT возвращает число, а не дату.
  • Для привычной недели чаще используй ISODOW, а не DOW.
  • При группировке по месяцу или кварталу не забывай добавлять год.
  • Для фильтра по конкретному периоду обычно лучше диапазон дат, а не EXTRACT в WHERE.
  • Для отчётов по месяцам часто удобнее DATE_TRUNC.
  • При работе с часами и днями внимательно относись к часовым поясам.
  • В MySQL для Unix timestamp используют UNIX_TIMESTAMP.
  • В SQLite вместо EXTRACT обычно используют STRFTIME.
  • В SQL Server похожая функция называется DATEPART.

Если сказать совсем просто: EXTRACT нужен тогда, когда дата слишком большая, а тебе нужна только одна её часть.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр