sqlpostgresqlmake-datemake-time

SQL make_date and make_time: Build Dates and Times from Integer Parts

How to build a date or time from separate integer report columns without format strings, and why make_date validates out-of-range parts.

3 λεπτά ανάγνωσηςReferencesql · postgresql · make-date · make-time · date-functions · mysql
Αυτό το άρθρο είναι προς το παρόν στα ρωσικά — η αγγλική μετάφραση βρίσκεται σε εξέλιξη.

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

Обе функции принимают целые числа и возвращают типизированное значение date или time, сразу проверяя каждую часть на допустимый диапазон. Это снимает два класса проблем разом: не нужно собирать промежуточную строку и угадывать её формат, и не нужно вручную проверять, что месяц лежит в 1..12, а день не выходит за границу месяца. Типичные источники таких разрозненных частей — выгрузки CSV, веб-формы и старые ERP, где год, месяц и день попали в отдельные колонки. Ниже разберём базовый синтаксис, сборку даты из колонок, поведение на некорректных частях и отличия в MySQL и ClickHouse.

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

make_date(year, month, day) возвращает date, make_time(hour, min, sec) возвращает time. Аргументы — целые числа (у секунд тип double precision, чтобы хранить доли). Никаких форматных строк вроде 'YYYY-MM-DD' и связанных с ними сюрпризов локали.

SELECT
  make_date(2024, 3, 15)   AS d,    -- 2024-03-15
  make_time(14, 30, 0)     AS t;    -- 14:30:00

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

SELECT make_time(9, 5, 30.5) AS t;  -- 09:05:30.5

Полную метку времени даёт make_timestamp(year, month, day, hour, min, sec), а версию с зоной — make_timestamptz(...).

Сборка даты из отдельных колонок

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

-- order_parts(order_id, y, m, d)
SELECT
  order_id,
  make_date(y, m, d) AS order_date
FROM order_parts;

make_date снимает боль конкатенации и CAST: не нужно собирать строку y || '-' || m || '-' || d и надеяться, что одиночная цифра месяца не сломает формат. Числа идут в функцию как есть.

Тот же приём удобен для фильтра по дате, собранной из параметров. Все заказы за конкретный день из трёх чисел:

SELECT id, user_id, amount
FROM orders
WHERE created_at::date = make_date(2024, 3, 15)
  AND status = 'paid';

Проверка диапазона частей

make_date не молчит при бессмыслице: значение вне допустимого диапазона вызывает ошибку, а не «уезжает» в соседний месяц, как делает арифметика интервалов.

SELECT make_date(2024, 13, 1);   -- ERROR: date field value out of range
SELECT make_date(2024, 2, 30);   -- ERROR: date field value out of range

Это и плюс, и подводный камень:

  • Плюс: грязные данные (месяц 13, день 0) обнаруживаются сразу, а не превращаются тихо в неверную дату.
  • Гочи: один битый ряд в большой выборке роняет весь запрос. Если данные не вычищены, проверяйте части заранее.

Защита от плохих строк через предварительный фильтр:

SELECT order_id, make_date(y, m, d) AS order_date
FROM order_parts
WHERE m BETWEEN 1 AND 12
  AND d BETWEEN 1 AND 31;

Отрицательный год трактуется как до нашей эры, поэтому make_date(-1, 1, 1) — это не ошибка, а 0001-01-01 BC. Ноль в году запрещён.

Когда выгоднее make_time

make_time особенно удобен, когда час и минута приходят из настроек или из джойна. Например, нормализуем «рабочее окно» отдела сотрудников к единому времени старта:

SELECT
  e.dept,
  COUNT(*)                       AS people,
  make_time(9, 0, 0)             AS shift_start
FROM employees e
GROUP BY e.dept;

Собранное time можно прибавить к дате и получить полноценный момент без разбора строк:

SELECT
  u.id,
  make_date(2024, 3, 15) + make_time(18, 0, 0) AS reminder_at
FROM users u
WHERE u.country = 'DE';

Различия в MySQL и ClickHouse

Имена и поведение отличаются от PostgreSQL.

  • MySQL функции make_date в привычном смысле нет. Есть MAKEDATE(year, dayofyear) — она ждёт год и день года, а не месяц с днём. Для сборки из месяца и дня берут STR_TO_DATE, а для времени — MAKETIME(hour, minute, second).
SELECT
  STR_TO_DATE('2024-3-15', '%Y-%c-%e') AS d,   -- 2024-03-15
  MAKETIME(14, 30, 0)                  AS t;   -- 14:30:00
  • ClickHouse предлагает makeDate(year, month, day) и makeDateTime(year, month, day, hour, minute, second), имена совпадают по смыслу с PostgreSQL.
SELECT
  makeDate(2024, 3, 15)                  AS d,
  makeDateTime(2024, 3, 15, 14, 30, 0)   AS t;

Главные расхождения при переносе сосредоточены именно в сборке даты из частей. В PostgreSQL make_date(y, m, d) ждёт год, месяц и день и падает с ошибкой на месяце 13 или дне 30 февраля. В MySQL MAKEDATE(year, dayofyear) принимает год и день года, поэтому те же три числа (2024, 3, 15) дадут не 15 марта, а 15-й день 2024 года, то есть 2024-01-15; никакой ошибки при этом не будет. В ClickHouse makeDate(year, month, day) повторяет сигнатуру PostgreSQL, но выход за диапазон не вызывает исключения — значение приводится к границе типа Date. Поэтому при миграции запроса проверяйте не только имя функции, но и порядок и смысл аргументов.

На практике это означает: после переноса прогоните на каждой СУБД одну и ту же тройку чисел с заведомо некорректной частью — например make_date(2024, 13, 1) против makeDate(2024, 13, 1) и MAKEDATE(2024, 13) — и сравните, что вернётся: ошибка, приведённая к границе дата или сдвиг по дню года. Так вы увидите расхождение до того, как его поймает отчёт.

Ключевое отличие при переносе: MySQL MAKEDATE принимает день года, а не месяц и день, и спокойно проглотит то, что PostgreSQL счёл бы ошибкой. Всегда сверяйте сигнатуру, прежде чем переносить запрос между СУБД.

Εξασκηθείτε σε πραγματικές ασκήσεις

Λύστε ασκήσεις στην εξάσκηση SQL με άμεση βαθμολόγηση και υποδείξεις.

Άνοιγμα εξάσκησης