sqlpostgresqldatesinterval

make_timestamp y make_interval en SQL: construir fechas e intervalos por partes

make_timestamp arma una fecha a partir de numeros y make_interval construye un intervalo con argumentos con nombre en lugar de concatenar cadenas.

4 min de lecturaReferencesql · postgresql · dates · interval · timestamp
Este artículo está actualmente en ruso — la traducción está en curso.

make_timestamp собирает значение timestamp из чисел года, месяца, дня, часа, минуты и секунд, а make_interval строит interval из именованных аргументов вроде days и hours. Эти функции PostgreSQL нужны там, где компоненты даты приходят раздельно — из полей формы, колонок отчёта, генератора календаря или параметра тарифа, — и склеивать из них строку с последующим кастом к timestamp рискованно. Сборка прямо из чисел не зависит от форматных строк, локали и настройки DateStyle.

Разница видна на параметризации. Срок в днях, который приходит переменной или плейсхолдером $1, нельзя вставить внутрь строкового литерала INTERVAL '... days', а в make_interval(days => $1) он подставляется как обычный аргумент. Точно так же make_timestamp(2024, 3, 15, ...) отдаёт тип timestamp без промежуточной строки, поэтому порядок MM/DD против DD/MM и нулевое дополнение перестают влиять на результат. Дальше разберём сборку даты, интервала и варианта с часовым поясом по очереди.

Дата из числовых частей

make_timestamp принимает год, месяц, день, час, минуту и секунды как обычные числа и возвращает timestamp. Никакого парсинга строк — а значит, никаких сюрпризов с порядком MM/DD против DD/MM.

SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;
-- 2024-03-15 14:30:00

Секунды — это double precision, так что дробная часть тоже работает: make_timestamp(2024, 3, 15, 14, 30, 7.5). Есть и родственники: make_date(2024, 3, 15) для чистой даты и make_time(14, 30, 0) для времени без даты.

Сравните с типичной склейкой строк — она хрупкая и зависит от настроек сессии:

-- fragile: depends on DateStyle and zero-padding
SELECT (y || '-' || m || '-' || d)::date FROM (SELECT 2024 y, 3 m, 15 d) s;

Если m равно 3, а не 03, или DateStyle настроен на DMY, такой код тихо вернёт не ту дату либо упадёт. make_timestamp решает обе проблемы разом.

Интервал из именованных аргументов

make_interval строит interval из именованных полей: years, months, weeks, days, hours, mins, secs. Передавайте только то, что нужно, — остальное по умолчанию ноль.

SELECT make_interval(days => 10, hours => 2) AS shipping_window;
-- 10 days 02:00:00

Главный выигрыш — параметризация. Когда срок задан переменным числом, его нельзя подставить в строковый литерал INTERVAL, зато легко передать в make_interval:

-- give every paid order a grace period of N days
SELECT o.id,
       o.created_at + make_interval(days => 14) AS grace_until
FROM orders o
WHERE o.status = 'paid';

Число 14 здесь может быть колонкой или плейсхолдером $1. Это ключевое отличие от строкового интервала: INTERVAL '$1 days' не работает, потому что плейсхолдер оказался бы внутри литерала, а make_interval(days => $1) — корректно. Альтернатива со склейкой и кастом (($1 || ' days')::interval) тоже сработает, но именованные аргументы читаются яснее и не зависят от того, как локаль печатает число.

-- per-row interval driven by data, not a constant string
SELECT u.id, u.email,
       u.created_at + make_interval(days => 30) AS trial_ends
FROM users u
WHERE u.country = 'DE';

make_timestamptz и часовой пояс

У make_timestamp есть версия с поясом — make_timestamptz. Дополнительный текстовый аргумент задаёт зону, в которой трактуются переданные числа; результат имеет тип timestamptz.

SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz;
-- stored as UTC, shown in your session zone

Без последнего аргумента числа трактуются в часовом поясе текущей сессии (TimeZone). Это удобно для дедлайнов, привязанных к конкретному региону:

SELECT u.id,
       make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff
FROM users u
WHERE u.country = 'BR';

Ловушка: аргументы make_* не «оборачиваются» автоматически. make_timestamp(2024, 13, 1, 0, 0, 0) не станет январём 2025 года — будет ошибка field value out of range. То же с make_date(2024, 2, 30). Если на входе непроверенные числа, валидируйте их заранее или ловите исключение.

Различия в других СУБД

make_timestamp, make_interval и компания — это PostgreSQL. В других движках подход иной:

  • MySQL: соберите дату через MAKEDATE/MAKETIME либо STR_TO_DATE('2024-03-15','%Y-%m-%d'). Интервалы задаются синтаксисом INTERVAL 10 DAY, а для переменного количества — INTERVAL n DAY, где n может быть выражением.
  • ClickHouse: используйте makeDateTime(2024, 3, 15, 14, 30, 0) и makeDateTime64(...) для долей секунды; зона передаётся отдельным аргументом. Интервал собирается через toIntervalDay(n), toIntervalHour(n) и их сложение.

При переносе между движками сходится не всё, и расходятся именно крайние значения. make_timestamp отдаёт timestamp без зоны, make_timestamptztimestamptz в UTC, тогда как makeDateTime в ClickHouse и MAKEDATE в MySQL трактуют зону по-своему; дробные секунды в PostgreSQL живут в аргументе secs, а в ClickHouse требуют отдельной makeDateTime64. Прежде чем полагаться на формулу, прогоните пограничные входы: 13-й месяц, 30 февраля, отрицательный аргумент интервала и пустую зону — make_* на них кидает field value out of range, а соседний движок может молча нормализовать.

Если сборка даты влияет на ключ, биллинг или дедлайн, держите её в одном слое и описывайте рядом, в какой зоне трактуются числа: одно и то же make_timestamptz(..., 'Europe/Berlin') и make_timestamp(...) дают разные абсолютные моменты. И помните про индексы: make_interval(days => $1) поверх колонки created_at в WHERE превращает условие в вычисляемое и может закрыть путь к индексу — переносите сборку интервала на сторону константы или диапазона, а не оборачивайте в неё индексируемую колонку.

Если код переносимый, изолируйте сборку в одном слое: на PostgreSQL — семейство make_*, на остальных — родные функции (makeDateTime, STR_TO_DATE, INTERVAL n DAY). Принцип один: не клейте даты из строк, когда движок умеет собирать timestamp и interval прямо из чисел.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador