Dan l-artiklu bħalissa huwa bir-Russu — it-traduzzjoni bl-Ingliż għaddejja.
DATE_PART вытаскивает из метки времени одну числовую часть: час, день недели, номер недели, секунды от эпохи. Это функциональная запись того же, что делает оператор EXTRACT, но поле здесь — обычная строка, а значит, его можно подставлять динамически.
DATE_PART пригождается там, где из колонки timestamp нужно вытащить не саму дату, а одно её поле как число: час события для почасовой витрины, день недели для отчёта по дням, номер ISO-недели для группировки, секунды от эпохи для расчёта возраста или длительности. Поскольку имя поля передаётся строкой, его удобно подставлять параметром, а результат double precision сразу годится для GROUP BY, AVG и арифметики. Главные тонкости касаются типа результата, нумерации дней недели ('dow' против 'isodow') и поведения 'epoch' на интервалах — их и разбираем ниже.
Что возвращает DATE_PART
Первый аргумент — имя поля строкой ('hour', 'dow', 'doy', 'week', 'month', 'epoch' и т. д.), второй — timestamp или date. Результат всегда double precision, то есть число, а не интервал или строка.
SELECT
DATE_PART('hour', TIMESTAMP '2026-06-17 14:32:09') AS hour,
DATE_PART('dow', TIMESTAMP '2026-06-17 14:32:09') AS day_of_week,
DATE_PART('doy', TIMESTAMP '2026-06-17 14:32:09') AS day_of_year,
DATE_PART('week', TIMESTAMP '2026-06-17 14:32:09') AS iso_week;
Полезные поля:
'hour', 'minute', 'second' — компоненты времени; 'second' включает дробную часть.
'year', 'month', 'day' — компоненты даты.
'dow' — день недели, 0 = воскресенье ... 6 = суббота.
'doy' — день года от 1 до 366.
'week' — номер недели по ISO 8601.
'epoch' — число секунд с 1970-01-01 00:00:00 UTC.
EXTRACT('hour' FROM ts) и DATE_PART('hour', ts) дают одно и то же число. Разница — синтаксическая: EXTRACT это оператор со словом FROM, а DATE_PART обычная функция.
SELECT EXTRACT(dow FROM created_at) FROM orders;
SELECT DATE_PART('dow', created_at) FROM orders;
Практический плюс функции в том, что поле — строковый литерал, и его легко передать как параметр. Можно сгруппировать заказы по любой части даты, не переписывая запрос:
SELECT
DATE_PART('hour', created_at) AS hour_of_day,
COUNT(*) AS orders
FROM orders
WHERE status = 'paid'
GROUP BY DATE_PART('hour', created_at)
ORDER BY hour_of_day;
В PostgreSQL 14+ EXTRACT возвращает numeric, а DATE_PART по-прежнему double precision; для сравнений и ROUND это иногда важно.
День недели: dow против isodow
Главная путаница — нумерация дней. 'dow' начинает неделю с воскресенья и нумерует его нулём; 'isodow' следует ISO, где понедельник 1, а воскресенье 7.
SELECT
DATE_PART('dow', DATE '2026-06-21') AS dow,
DATE_PART('isodow', DATE '2026-06-21') AS isodow;
Типичный отчёт «активность по дням недели» с человекочитаемой меткой:
SELECT
DATE_PART('isodow', created_at) AS weekday_num,
TO_CHAR(created_at, 'Dy') AS weekday,
COUNT(*) AS signups
FROM users
GROUP BY DATE_PART('isodow', created_at), TO_CHAR(created_at, 'Dy')
ORDER BY weekday_num;
Ловушка: если фильтровать выходные через DATE_PART('dow', d) IN (0, 6), а потом случайно перейти на isodow, суббота и воскресенье станут 6 и 7 — условие IN (0, 6) молча начнёт пропускать воскресенье. Всегда фиксируйте, какое именно поле используете.
Эпоха и разница во времени
Поле 'epoch' превращает метку в секунды, а из интервала достаёт его полную длительность в секундах. Это удобный способ посчитать возраст или длительность одним числом.
SELECT
id,
DATE_PART('epoch', NOW() - created_at) / 86400 AS account_age_days
FROM users
ORDER BY account_age_days DESC
LIMIT 10;
Та же идея для бизнес-метрики «средний возраст заказа в часах» по отделам сотрудников:
SELECT
e.dept,
AVG(DATE_PART('epoch', NOW() - o.created_at) / 3600) AS avg_order_age_hours
FROM orders o
JOIN users u ON u.id = o.user_id
JOIN employees e ON e.name = u.name
GROUP BY e.dept;
Помните: DATE_PART('epoch', interval) для интервала длиннее суток вернёт полные секунды, а не «часть дня», в отличие от 'hour', который у интервала даёт только часовую компоненту.
Различия в MySQL и ClickHouse
Имя DATE_PART не универсально.
- MySQL функции
DATE_PART нет. Используйте EXTRACT(HOUR FROM ts) или отдельные функции HOUR(), DAYOFWEEK() (где воскресенье 1), DAYOFYEAR(), WEEK().
SELECT
HOUR(created_at) AS hour_of_day,
DAYOFWEEK(created_at) AS dow_sun_is_1,
COUNT(*) AS orders
FROM orders
GROUP BY HOUR(created_at), DAYOFWEEK(created_at);
- ClickHouse предлагает
toHour, toDayOfWeek (понедельник 1), toDayOfYear, toUnixTimestamp для эпохи.
SELECT
toHour(created_at) AS hour_of_day,
toDayOfWeek(created_at) AS dow_mon_is_1,
count() AS orders
FROM orders
GROUP BY hour_of_day, dow_mon_is_1;
Главная засада при переносе — именно нумерация дней недели: DATE_PART('dow', d) в PostgreSQL даёт 0 для воскресенья, DAYOFWEEK() в MySQL для того же воскресенья даёт 1, а toDayOfWeek в ClickHouse начинает неделю с понедельника (1). Любой фильтр или CASE, завязанный на конкретные числа дней, после смены СУБД нужно пересчитывать заново. Вторая тонкость — тип результата: DATE_PART всегда возвращает double precision, тогда как HOUR() и подобные функции MySQL отдают целое, а EXTRACT в PostgreSQL 14+ — numeric, и при сравнении дробных секунд это иногда расходится.
Отдельно стоит помнить про индексы: условие вида DATE_PART('year', created_at) = 2026 оборачивает колонку в функцию, поэтому обычный B-tree-индекс по created_at не используется и запрос идёт по полному скану. Если фильтр горячий, либо переписывайте его как диапазон created_at >= DATE '2026-01-01' AND created_at < DATE '2027-01-01', либо заводите выражательный индекс прямо по DATE_PART('year', created_at). Для группировок в отчётах это не критично, но в WHERE по большой таблице разница в плане выполнения видна сразу.
Суть везде одна — достать число из даты, но нумерация дней недели у каждой СУБД своя, и это первое, что стоит проверить при переносе запроса.
DATE_PARTвытаскивает из метки времени одну числовую часть: час, день недели, номер недели, секунды от эпохи. Это функциональная запись того же, что делает операторEXTRACT, но поле здесь — обычная строка, а значит, его можно подставлять динамически.DATE_PARTпригождается там, где из колонкиtimestampнужно вытащить не саму дату, а одно её поле как число: час события для почасовой витрины, день недели для отчёта по дням, номер ISO-недели для группировки, секунды от эпохи для расчёта возраста или длительности. Поскольку имя поля передаётся строкой, его удобно подставлять параметром, а результатdouble precisionсразу годится дляGROUP BY,AVGи арифметики. Главные тонкости касаются типа результата, нумерации дней недели ('dow'против'isodow') и поведения'epoch'на интервалах — их и разбираем ниже.Что возвращает DATE_PART
Первый аргумент — имя поля строкой (
'hour','dow','doy','week','month','epoch'и т. д.), второй —timestampилиdate. Результат всегдаdouble precision, то есть число, а не интервал или строка.SELECT DATE_PART('hour', TIMESTAMP '2026-06-17 14:32:09') AS hour, DATE_PART('dow', TIMESTAMP '2026-06-17 14:32:09') AS day_of_week, DATE_PART('doy', TIMESTAMP '2026-06-17 14:32:09') AS day_of_year, DATE_PART('week', TIMESTAMP '2026-06-17 14:32:09') AS iso_week; -- 14 | 3 | 168 | 25Полезные поля:
'hour','minute','second'— компоненты времени;'second'включает дробную часть.'year','month','day'— компоненты даты.'dow'— день недели,0= воскресенье ...6= суббота.'doy'— день года от1до366.'week'— номер недели по ISO 8601.'epoch'— число секунд с1970-01-01 00:00:00 UTC.DATE_PART против EXTRACT
EXTRACT('hour' FROM ts)иDATE_PART('hour', ts)дают одно и то же число. Разница — синтаксическая:EXTRACTэто оператор со словомFROM, аDATE_PARTобычная функция.-- These two lines are equivalent SELECT EXTRACT(dow FROM created_at) FROM orders; SELECT DATE_PART('dow', created_at) FROM orders;Практический плюс функции в том, что поле — строковый литерал, и его легко передать как параметр. Можно сгруппировать заказы по любой части даты, не переписывая запрос:
SELECT DATE_PART('hour', created_at) AS hour_of_day, COUNT(*) AS orders FROM orders WHERE status = 'paid' GROUP BY DATE_PART('hour', created_at) ORDER BY hour_of_day;В PostgreSQL 14+
EXTRACTвозвращаетnumeric, аDATE_PARTпо-прежнемуdouble precision; для сравнений иROUNDэто иногда важно.День недели: dow против isodow
Главная путаница — нумерация дней.
'dow'начинает неделю с воскресенья и нумерует его нулём;'isodow'следует ISO, где понедельник1, а воскресенье7.SELECT DATE_PART('dow', DATE '2026-06-21') AS dow, -- Sunday -> 0 DATE_PART('isodow', DATE '2026-06-21') AS isodow; -- Sunday -> 7Типичный отчёт «активность по дням недели» с человекочитаемой меткой:
SELECT DATE_PART('isodow', created_at) AS weekday_num, TO_CHAR(created_at, 'Dy') AS weekday, COUNT(*) AS signups FROM users GROUP BY DATE_PART('isodow', created_at), TO_CHAR(created_at, 'Dy') ORDER BY weekday_num;Ловушка: если фильтровать выходные через
DATE_PART('dow', d) IN (0, 6), а потом случайно перейти наisodow, суббота и воскресенье станут6и7— условиеIN (0, 6)молча начнёт пропускать воскресенье. Всегда фиксируйте, какое именно поле используете.Эпоха и разница во времени
Поле
'epoch'превращает метку в секунды, а из интервала достаёт его полную длительность в секундах. Это удобный способ посчитать возраст или длительность одним числом.SELECT id, DATE_PART('epoch', NOW() - created_at) / 86400 AS account_age_days FROM users ORDER BY account_age_days DESC LIMIT 10;Та же идея для бизнес-метрики «средний возраст заказа в часах» по отделам сотрудников:
SELECT e.dept, AVG(DATE_PART('epoch', NOW() - o.created_at) / 3600) AS avg_order_age_hours FROM orders o JOIN users u ON u.id = o.user_id JOIN employees e ON e.name = u.name GROUP BY e.dept;Помните:
DATE_PART('epoch', interval)для интервала длиннее суток вернёт полные секунды, а не «часть дня», в отличие от'hour', который у интервала даёт только часовую компоненту.Различия в MySQL и ClickHouse
Имя
DATE_PARTне универсально.DATE_PARTнет. ИспользуйтеEXTRACT(HOUR FROM ts)или отдельные функцииHOUR(),DAYOFWEEK()(где воскресенье1),DAYOFYEAR(),WEEK().SELECT HOUR(created_at) AS hour_of_day, DAYOFWEEK(created_at) AS dow_sun_is_1, COUNT(*) AS orders FROM orders GROUP BY HOUR(created_at), DAYOFWEEK(created_at);toHour,toDayOfWeek(понедельник1),toDayOfYear,toUnixTimestampдля эпохи.SELECT toHour(created_at) AS hour_of_day, toDayOfWeek(created_at) AS dow_mon_is_1, count() AS orders FROM orders GROUP BY hour_of_day, dow_mon_is_1;Главная засада при переносе — именно нумерация дней недели:
DATE_PART('dow', d)в PostgreSQL даёт0для воскресенья,DAYOFWEEK()в MySQL для того же воскресенья даёт1, аtoDayOfWeekв ClickHouse начинает неделю с понедельника (1). Любой фильтр илиCASE, завязанный на конкретные числа дней, после смены СУБД нужно пересчитывать заново. Вторая тонкость — тип результата:DATE_PARTвсегда возвращаетdouble precision, тогда какHOUR()и подобные функции MySQL отдают целое, аEXTRACTв PostgreSQL 14+ —numeric, и при сравнении дробных секунд это иногда расходится.Отдельно стоит помнить про индексы: условие вида
DATE_PART('year', created_at) = 2026оборачивает колонку в функцию, поэтому обычный B-tree-индекс поcreated_atне используется и запрос идёт по полному скану. Если фильтр горячий, либо переписывайте его как диапазонcreated_at >= DATE '2026-01-01' AND created_at < DATE '2027-01-01', либо заводите выражательный индекс прямо поDATE_PART('year', created_at). Для группировок в отчётах это не критично, но вWHEREпо большой таблице разница в плане выполнения видна сразу.Суть везде одна — достать число из даты, но нумерация дней недели у каждой СУБД своя, и это первое, что стоит проверить при переносе запроса.