В PostgreSQL есть удобное семейство функций make_*, которое помогает собирать дату, время и интервалы не из строк, а из отдельных числовых частей.
Например, у вас есть год, месяц, день, час и минута. Они пришли из формы, лежат в разных колонках отчёта или генерируются в запросе. Можно, конечно, склеить строку вроде '2024-03-15 14:30:00', а потом привести её к дате. Но это хрупкий путь: появляются вопросы формата, порядка дня и месяца, лишних пробелов, настроек сессии и человеческих опечаток.
Гораздо надёжнее сказать базе прямо:
вот год, вот месяц, вот день, вот часы, минуты и секунды — собери из этого нормальный timestamp.
Для этого и нужны функции make_timestamp, make_timestamptz, make_date, make_time и make_interval.
Зачем нужны make_timestamp и make_interval
Главная идея простая: не склеивать дату руками из строки, если PostgreSQL умеет собрать её из чисел.
Функция make_timestamp собирает значение типа timestamp из отдельных частей:
- год;
- месяц;
- день;
- час;
- минута;
- секунды.
А функция make_interval собирает значение типа interval из именованных аргументов:
- годы;
- месяцы;
- недели;
- дни;
- часы;
- минуты;
- секунды.
Это особенно удобно, когда данные приходят раздельно. Например:
- в форме пользователь выбирает год, месяц и день отдельно;
- в таблице лежат колонки
year, month, day;
- тариф хранит длительность пробного периода в днях;
- отчёт строит календарь по частям;
- API присылает дедлайн отдельными полями.
В таких случаях строковая сборка быстро превращается в болото:
SELECT (y || '-' || m || '-' || d)::date AS created_date
FROM (
SELECT 2024 AS y, 3 AS m, 15 AS d
) s;
На маленьком примере выглядит безобидно. Но в реальном коде появляются нюансы: где-то забыли ноль, где-то день и месяц пришли в другом порядке, где-то значение оказалось пустым, где-то формат поменялся.
С make_timestamp код получается прямее и спокойнее:
SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;
Результат:
2024-03-15 14:30:00
Мы не просим PostgreSQL распарсить строку. Мы даём ему числа, а он собирает из них значение нужного типа.
make_timestamp: дата и время из числовых частей
make_timestamp принимает шесть аргументов:
make_timestamp(year, month, day, hour, min, sec)
Пример:
SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS order_created_at;
Результат:
2024-03-15 14:30:00
Такой запрос можно читать почти как обычную фразу:
собери timestamp: 2024 год, 3 месяц, 15 день, 14 часов, 30 минут, 0 секунд.
Это намного понятнее, чем строковая склейка, особенно для начинающего.
Секунды могут быть дробными, потому что последний аргумент имеет тип double precision.
SELECT make_timestamp(2024, 3, 15, 14, 30, 7.5) AS ts;
Результат:
2024-03-15 14:30:07.5
То есть можно собрать время не только с точностью до секунды, но и с долями секунды.
Родственные функции: make_date и make_time
Если вам нужна только дата без времени, используйте make_date.
SELECT make_date(2024, 3, 15) AS report_date;
Результат:
2024-03-15
Если нужно только время без даты, есть make_time.
SELECT make_time(14, 30, 0) AS starts_at;
Результат:
14:30:00
Получается аккуратная семья функций:
make_date — собрать дату;
make_time — собрать время;
make_timestamp — собрать дату и время;
make_timestamptz — собрать дату и время с учётом часового пояса;
make_interval — собрать интервал.
Для новичка это хороший ориентир: если видите make_, скорее всего, PostgreSQL собирает значение из отдельных частей.
Почему строковая сборка хуже
Представим таблицу с заказами, где дата доставки хранится отдельными частями.
SELECT
order_id,
make_timestamp(delivery_year, delivery_month, delivery_day, 10, 0, 0) AS delivery_at
FROM orders;
Такой запрос явно говорит: у каждого заказа берём год, месяц и день, а время ставим на 10 утра.
Строковый вариант выглядел бы примерно так:
SELECT
order_id,
(delivery_year || '-' || delivery_month || '-' || delivery_day || ' 10:00:00')::timestamp AS delivery_at
FROM orders;
Здесь уже приходится мысленно проверять:
- правильно ли стоят дефисы;
- нет ли лишних пробелов;
- всегда ли части даты заполнены;
- не перепутаны ли день и месяц;
- корректно ли строка приводится к
timestamp.
make_timestamp убирает лишний слой. Вместо «собери строку, а потом попробуй понять её как дату» мы сразу собираем дату из чисел.
make_interval: интервал из понятных частей
Теперь перейдём к интервалам.
interval в PostgreSQL — это длительность. Например:
- 10 дней;
- 2 часа;
- 3 месяца;
- 1 год и 15 минут.
Функция make_interval позволяет собрать такой интервал из именованных аргументов.
SELECT make_interval(days => 10, hours => 2) AS shipping_window;
Результат:
10 days 02:00:00
Самое приятное здесь — не нужно передавать все части. Указываем только то, что нужно, остальное PostgreSQL считает нулём.
Например, интервал на 30 дней:
SELECT make_interval(days => 30) AS trial_period;
Интервал на 1 месяц и 12 часов:
SELECT make_interval(months => 1, hours => 12) AS access_period;
Интервал на 2 недели:
SELECT make_interval(weeks => 2) AS review_period;
Именованные аргументы делают запрос очень читаемым. Когда человек видит days => 10, ему не надо вспоминать порядок аргументов. Сразу понятно: речь о десяти днях.
Практический пример: дата окончания пробного периода
Допустим, у нас есть таблица пользователей. У каждого пользователя есть дата регистрации, а пробный период длится 30 дней.
SELECT
u.id,
u.email,
u.created_at,
u.created_at + make_interval(days => 30) AS trial_ends_at
FROM users u;
Здесь PostgreSQL берёт дату регистрации и прибавляет к ней интервал в 30 дней.
Результат может выглядеть так:
id | email | created_at | trial_ends_at
---+------------------+---------------------+---------------------
1 | ann@example.com | 2024-03-01 09:15:00 | 2024-03-31 09:15:00
2 | max@example.com | 2024-03-10 18:20:00 | 2024-04-09 18:20:00
Запрос читается естественно:
дата регистрации плюс интервал в 30 дней — это дата окончания пробного периода.
Почему make_interval удобен для параметров
Главный выигрыш make_interval становится заметен, когда количество дней приходит не как постоянное число, а как параметр.
Например, в приложении пользователь выбрал срок доставки: 14 дней. Это значение приходит в SQL как параметр.
С make_interval всё просто:
SELECT
o.id,
o.created_at + make_interval(days => $1) AS grace_until
FROM orders o
WHERE o.status = 'paid';
Параметр $1 подставляется как обычное число.
А вот так писать не стоит:
SELECT
o.id,
o.created_at + INTERVAL '$1 days' AS grace_until
FROM orders o;
Такой подход не работает так, как часто ожидают новички. Плейсхолдер оказался внутри строкового литерала, а значит, PostgreSQL не воспринимает его как параметр запроса.
Можно было бы склеивать строку и приводить её к interval:
SELECT
o.id,
o.created_at + ($1 || ' days')::interval AS grace_until
FROM orders o;
Но это снова возвращает нас к строкам. Код становится менее чистым: мы собираем текст, а потом просим базу понять этот текст как интервал.
С make_interval намерение видно сразу:
SELECT
o.id,
o.created_at + make_interval(days => $1) AS grace_until
FROM orders o;
Это читается лучше и безопаснее по смыслу: параметр остаётся числом, а не частью строки.
Интервал может зависеть от данных в таблице
make_interval полезен не только с параметрами. В него можно передавать значения из колонок.
Допустим, у разных тарифов разная длительность пробного периода.
SELECT
u.id,
u.email,
p.name AS plan_name,
u.created_at + make_interval(days => p.trial_days) AS trial_ends_at
FROM users u
JOIN plans p ON p.id = u.plan_id;
Здесь у каждого пользователя дата окончания пробного периода считается по его тарифу.
Если у одного тарифа trial_days = 7, а у другого trial_days = 30, запрос сам подставит нужное значение для каждой строки.
Это хороший пример того, как SQL работает «построчно»: для каждой строки берутся свои данные, из них собирается свой интервал, потом он прибавляется к дате.
make_timestamptz: дата и время с часовым поясом
make_timestamp возвращает timestamp без часового пояса.
Иногда этого достаточно. Например, если вы храните локальное время события и отдельно знаете, к какому региону оно относится.
Но для дедлайнов, международных проектов, рассылок и биллинга часто важен конкретный часовой пояс. Тогда используйте make_timestamptz.
SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz;
Здесь числа трактуются как дата и время в зоне Europe/Berlin.
Важно понимать: timestamptz в PostgreSQL хранит не «дату с красивой подписью часового пояса», а конкретный момент времени. Показываться он будет в часовом поясе текущей сессии.
То есть один и тот же момент может отображаться по-разному в разных сессиях, но внутри это тот же самый момент.
Пример с дедлайном для региона
Допустим, для пользователей из Бразилии дедлайн акции наступает 31 декабря 2024 года в 23:59:59 по времени Сан-Паулу.
SELECT
u.id,
u.email,
make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff_at
FROM users u
WHERE u.country = 'BR';
Здесь мы явно говорим PostgreSQL:
эти числа нужно понимать как локальное время в зоне America/Sao_Paulo.
Это лучше, чем собирать строку и надеяться, что все правильно поймут, где именно наступает дедлайн.
Что будет без часового пояса
У make_timestamptz можно не указывать последний аргумент с зоной. Тогда PostgreSQL будет использовать часовой пояс текущей сессии.
SELECT make_timestamptz(2024, 3, 15, 14, 30, 0) AS ts_tz;
Но в важных местах лучше не полагаться на неявную настройку сессии. Если дедлайн привязан к конкретной стране или городу, укажите зону явно.
Так код будет понятен и вам, и будущему разработчику, который откроет запрос через полгода.
Важная ловушка: PostgreSQL не исправляет неверные даты
Функции make_* не превращают некорректные значения в корректные автоматически.
Например, 13-й месяц не станет январём следующего года:
SELECT make_timestamp(2024, 13, 1, 0, 0, 0) AS ts;
PostgreSQL выдаст ошибку, потому что месяца с номером 13 не существует.
То же самое с несуществующей датой:
SELECT make_date(2024, 2, 30) AS d;
30 февраля не существует, поэтому будет ошибка.
Это не недостаток, а полезная защита. База не должна молча угадывать, что вы имели в виду. Если на вход могут прийти непроверенные числа, их нужно валидировать заранее.
Например, до сборки даты можно проверить, что месяц лежит в диапазоне от 1 до 12, а день — в разумном диапазоне. Но окончательную проверку календарной корректности всё равно удобно оставить PostgreSQL: он точно знает, есть ли такая дата в календаре.
Где эти функции особенно полезны
make_timestamp и make_interval хорошо подходят для ситуаций, где дата или длительность собирается из данных.
Например, генерация календаря:
SELECT make_date(2024, month_number, 1) AS month_start
FROM generate_series(1, 12) AS month_number;
Результат:
2024-01-01
2024-02-01
2024-03-01
...
2024-12-01
Или расчёт даты доставки:
SELECT
o.id,
o.created_at,
o.created_at + make_interval(days => o.delivery_days) AS expected_delivery_at
FROM orders o;
Или сборка времени начала смены:
SELECT
e.id,
e.name,
make_timestamp(s.year, s.month, s.day, s.hour, s.minute, 0) AS shift_starts_at
FROM shifts s
JOIN employees e ON e.id = s.employee_id;
В каждом примере мы не играем со строками. Мы работаем с типами: числами, датами, временем и интервалами.
Как это влияет на индексы
Сами по себе make_timestamp и make_interval не плохи для производительности. Но важно, где именно вы их используете.
Представим, что в таблице orders есть индекс по created_at.
Хорошее условие:
SELECT *
FROM orders
WHERE created_at >= make_timestamp(2024, 3, 1, 0, 0, 0)
AND created_at < make_timestamp(2024, 4, 1, 0, 0, 0);
Здесь колонка created_at остаётся «чистой». PostgreSQL может использовать индекс по этой колонке.
А вот такой стиль обычно хуже:
SELECT *
FROM orders
WHERE created_at + make_interval(days => 14) >= now();
Здесь для каждой строки нужно вычислить выражение поверх created_at. Индексу сложнее помочь, потому что сравнивается уже не сама колонка, а результат вычисления.
Часто условие можно переписать так:
SELECT *
FROM orders
WHERE created_at >= now() - make_interval(days => 14);
Теперь справа вычисляется граница диапазона, а слева остаётся обычная колонка. Такой запрос чаще дружит с индексом.
Принцип простой:
по возможности не оборачивайте индексируемую колонку в функцию или арифметическое выражение. Лучше вычислите константу справа и сравните колонку с ней.
Отличия от других СУБД
Функции make_timestamp, make_timestamptz, make_date, make_time и make_interval — это PostgreSQL.
В других базах похожие задачи решаются иначе.
В MySQL для даты и времени часто используют функции вроде MAKEDATE, MAKETIME, STR_TO_DATE, а интервалы задают через синтаксис INTERVAL.
Например:
SELECT DATE_ADD(created_at, INTERVAL 10 DAY) AS expires_at
FROM users;
Для переменного количества дней в MySQL можно использовать выражение:
SELECT DATE_ADD(created_at, INTERVAL trial_days DAY) AS trial_ends_at
FROM users;
В ClickHouse есть свои функции для сборки даты и времени, например makeDateTime.
SELECT makeDateTime(2024, 3, 15, 14, 30, 0) AS ts;
Для интервалов в ClickHouse используются функции вроде toIntervalDay, toIntervalHour и похожие.
SELECT created_at + toIntervalDay(10) AS expires_at
FROM users;
При переносе SQL между движками важно не копировать функции механически. Смысл похожий, но детали отличаются:
- как трактуется часовой пояс;
- что происходит с некорректной датой;
- как задаются дробные секунды;
- можно ли передавать переменное количество дней;
- как база ведёт себя на пограничных значениях.
Если сборка даты влияет на биллинг, дедлайн, ключ отчёта или юридически важное время, такие места лучше проверять особенно внимательно.
Главное правило
Не склеивайте дату из строк без необходимости.
Если у вас уже есть отдельные числа — год, месяц, день, час, минута, секунды — используйте make_timestamp, make_date или make_time.
Если вам нужно собрать длительность — используйте make_interval.
Если важен часовой пояс — используйте make_timestamptz и явно указывайте зону.
Такой код получается:
- понятнее;
- надёжнее;
- легче параметризуется;
- меньше зависит от формата строк;
- лучше читается на ревью;
- проще поддерживается в больших запросах.
Для новичка это важная привычка: SQL работает не только со строками. У дат, времени и интервалов есть свои типы, и с ними лучше работать напрямую.
В PostgreSQL есть удобное семейство функций
make_*, которое помогает собирать дату, время и интервалы не из строк, а из отдельных числовых частей.Например, у вас есть год, месяц, день, час и минута. Они пришли из формы, лежат в разных колонках отчёта или генерируются в запросе. Можно, конечно, склеить строку вроде
'2024-03-15 14:30:00', а потом привести её к дате. Но это хрупкий путь: появляются вопросы формата, порядка дня и месяца, лишних пробелов, настроек сессии и человеческих опечаток.Гораздо надёжнее сказать базе прямо:
Для этого и нужны функции
make_timestamp,make_timestamptz,make_date,make_timeиmake_interval.Зачем нужны
make_timestampиmake_intervalГлавная идея простая: не склеивать дату руками из строки, если PostgreSQL умеет собрать её из чисел.
Функция
make_timestampсобирает значение типаtimestampиз отдельных частей:А функция
make_intervalсобирает значение типаintervalиз именованных аргументов:Это особенно удобно, когда данные приходят раздельно. Например:
year,month,day;В таких случаях строковая сборка быстро превращается в болото:
SELECT (y || '-' || m || '-' || d)::date AS created_date FROM ( SELECT 2024 AS y, 3 AS m, 15 AS d ) s;На маленьком примере выглядит безобидно. Но в реальном коде появляются нюансы: где-то забыли ноль, где-то день и месяц пришли в другом порядке, где-то значение оказалось пустым, где-то формат поменялся.
С
make_timestampкод получается прямее и спокойнее:SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;Результат:
Мы не просим PostgreSQL распарсить строку. Мы даём ему числа, а он собирает из них значение нужного типа.
make_timestamp: дата и время из числовых частейmake_timestampпринимает шесть аргументов:make_timestamp(year, month, day, hour, min, sec)Пример:
SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS order_created_at;Результат:
Такой запрос можно читать почти как обычную фразу:
Это намного понятнее, чем строковая склейка, особенно для начинающего.
Секунды могут быть дробными, потому что последний аргумент имеет тип
double precision.SELECT make_timestamp(2024, 3, 15, 14, 30, 7.5) AS ts;Результат:
То есть можно собрать время не только с точностью до секунды, но и с долями секунды.
Родственные функции:
make_dateиmake_timeЕсли вам нужна только дата без времени, используйте
make_date.SELECT make_date(2024, 3, 15) AS report_date;Результат:
Если нужно только время без даты, есть
make_time.SELECT make_time(14, 30, 0) AS starts_at;Результат:
Получается аккуратная семья функций:
make_date— собрать дату;make_time— собрать время;make_timestamp— собрать дату и время;make_timestamptz— собрать дату и время с учётом часового пояса;make_interval— собрать интервал.Для новичка это хороший ориентир: если видите
make_, скорее всего, PostgreSQL собирает значение из отдельных частей.Почему строковая сборка хуже
Представим таблицу с заказами, где дата доставки хранится отдельными частями.
SELECT order_id, make_timestamp(delivery_year, delivery_month, delivery_day, 10, 0, 0) AS delivery_at FROM orders;Такой запрос явно говорит: у каждого заказа берём год, месяц и день, а время ставим на 10 утра.
Строковый вариант выглядел бы примерно так:
SELECT order_id, (delivery_year || '-' || delivery_month || '-' || delivery_day || ' 10:00:00')::timestamp AS delivery_at FROM orders;Здесь уже приходится мысленно проверять:
timestamp.make_timestampубирает лишний слой. Вместо «собери строку, а потом попробуй понять её как дату» мы сразу собираем дату из чисел.make_interval: интервал из понятных частейТеперь перейдём к интервалам.
intervalв PostgreSQL — это длительность. Например:Функция
make_intervalпозволяет собрать такой интервал из именованных аргументов.SELECT make_interval(days => 10, hours => 2) AS shipping_window;Результат:
Самое приятное здесь — не нужно передавать все части. Указываем только то, что нужно, остальное PostgreSQL считает нулём.
Например, интервал на 30 дней:
SELECT make_interval(days => 30) AS trial_period;Интервал на 1 месяц и 12 часов:
SELECT make_interval(months => 1, hours => 12) AS access_period;Интервал на 2 недели:
SELECT make_interval(weeks => 2) AS review_period;Именованные аргументы делают запрос очень читаемым. Когда человек видит
days => 10, ему не надо вспоминать порядок аргументов. Сразу понятно: речь о десяти днях.Практический пример: дата окончания пробного периода
Допустим, у нас есть таблица пользователей. У каждого пользователя есть дата регистрации, а пробный период длится 30 дней.
SELECT u.id, u.email, u.created_at, u.created_at + make_interval(days => 30) AS trial_ends_at FROM users u;Здесь PostgreSQL берёт дату регистрации и прибавляет к ней интервал в 30 дней.
Результат может выглядеть так:
Запрос читается естественно:
Почему
make_intervalудобен для параметровГлавный выигрыш
make_intervalстановится заметен, когда количество дней приходит не как постоянное число, а как параметр.Например, в приложении пользователь выбрал срок доставки: 14 дней. Это значение приходит в SQL как параметр.
С
make_intervalвсё просто:SELECT o.id, o.created_at + make_interval(days => $1) AS grace_until FROM orders o WHERE o.status = 'paid';Параметр
$1подставляется как обычное число.А вот так писать не стоит:
SELECT o.id, o.created_at + INTERVAL '$1 days' AS grace_until FROM orders o;Такой подход не работает так, как часто ожидают новички. Плейсхолдер оказался внутри строкового литерала, а значит, PostgreSQL не воспринимает его как параметр запроса.
Можно было бы склеивать строку и приводить её к
interval:SELECT o.id, o.created_at + ($1 || ' days')::interval AS grace_until FROM orders o;Но это снова возвращает нас к строкам. Код становится менее чистым: мы собираем текст, а потом просим базу понять этот текст как интервал.
С
make_intervalнамерение видно сразу:SELECT o.id, o.created_at + make_interval(days => $1) AS grace_until FROM orders o;Это читается лучше и безопаснее по смыслу: параметр остаётся числом, а не частью строки.
Интервал может зависеть от данных в таблице
make_intervalполезен не только с параметрами. В него можно передавать значения из колонок.Допустим, у разных тарифов разная длительность пробного периода.
SELECT u.id, u.email, p.name AS plan_name, u.created_at + make_interval(days => p.trial_days) AS trial_ends_at FROM users u JOIN plans p ON p.id = u.plan_id;Здесь у каждого пользователя дата окончания пробного периода считается по его тарифу.
Если у одного тарифа
trial_days = 7, а у другогоtrial_days = 30, запрос сам подставит нужное значение для каждой строки.Это хороший пример того, как SQL работает «построчно»: для каждой строки берутся свои данные, из них собирается свой интервал, потом он прибавляется к дате.
make_timestamptz: дата и время с часовым поясомmake_timestampвозвращаетtimestampбез часового пояса.Иногда этого достаточно. Например, если вы храните локальное время события и отдельно знаете, к какому региону оно относится.
Но для дедлайнов, международных проектов, рассылок и биллинга часто важен конкретный часовой пояс. Тогда используйте
make_timestamptz.SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Berlin') AS ts_tz;Здесь числа трактуются как дата и время в зоне
Europe/Berlin.Важно понимать:
timestamptzв PostgreSQL хранит не «дату с красивой подписью часового пояса», а конкретный момент времени. Показываться он будет в часовом поясе текущей сессии.То есть один и тот же момент может отображаться по-разному в разных сессиях, но внутри это тот же самый момент.
Пример с дедлайном для региона
Допустим, для пользователей из Бразилии дедлайн акции наступает 31 декабря 2024 года в 23:59:59 по времени Сан-Паулу.
SELECT u.id, u.email, make_timestamptz(2024, 12, 31, 23, 59, 59, 'America/Sao_Paulo') AS cutoff_at FROM users u WHERE u.country = 'BR';Здесь мы явно говорим PostgreSQL:
Это лучше, чем собирать строку и надеяться, что все правильно поймут, где именно наступает дедлайн.
Что будет без часового пояса
У
make_timestamptzможно не указывать последний аргумент с зоной. Тогда PostgreSQL будет использовать часовой пояс текущей сессии.SELECT make_timestamptz(2024, 3, 15, 14, 30, 0) AS ts_tz;Но в важных местах лучше не полагаться на неявную настройку сессии. Если дедлайн привязан к конкретной стране или городу, укажите зону явно.
Так код будет понятен и вам, и будущему разработчику, который откроет запрос через полгода.
Важная ловушка: PostgreSQL не исправляет неверные даты
Функции
make_*не превращают некорректные значения в корректные автоматически.Например, 13-й месяц не станет январём следующего года:
SELECT make_timestamp(2024, 13, 1, 0, 0, 0) AS ts;PostgreSQL выдаст ошибку, потому что месяца с номером 13 не существует.
То же самое с несуществующей датой:
SELECT make_date(2024, 2, 30) AS d;30 февраля не существует, поэтому будет ошибка.
Это не недостаток, а полезная защита. База не должна молча угадывать, что вы имели в виду. Если на вход могут прийти непроверенные числа, их нужно валидировать заранее.
Например, до сборки даты можно проверить, что месяц лежит в диапазоне от 1 до 12, а день — в разумном диапазоне. Но окончательную проверку календарной корректности всё равно удобно оставить PostgreSQL: он точно знает, есть ли такая дата в календаре.
Где эти функции особенно полезны
make_timestampиmake_intervalхорошо подходят для ситуаций, где дата или длительность собирается из данных.Например, генерация календаря:
SELECT make_date(2024, month_number, 1) AS month_start FROM generate_series(1, 12) AS month_number;Результат:
Или расчёт даты доставки:
SELECT o.id, o.created_at, o.created_at + make_interval(days => o.delivery_days) AS expected_delivery_at FROM orders o;Или сборка времени начала смены:
SELECT e.id, e.name, make_timestamp(s.year, s.month, s.day, s.hour, s.minute, 0) AS shift_starts_at FROM shifts s JOIN employees e ON e.id = s.employee_id;В каждом примере мы не играем со строками. Мы работаем с типами: числами, датами, временем и интервалами.
Как это влияет на индексы
Сами по себе
make_timestampиmake_intervalне плохи для производительности. Но важно, где именно вы их используете.Представим, что в таблице
ordersесть индекс поcreated_at.Хорошее условие:
SELECT * FROM orders WHERE created_at >= make_timestamp(2024, 3, 1, 0, 0, 0) AND created_at < make_timestamp(2024, 4, 1, 0, 0, 0);Здесь колонка
created_atостаётся «чистой». PostgreSQL может использовать индекс по этой колонке.А вот такой стиль обычно хуже:
SELECT * FROM orders WHERE created_at + make_interval(days => 14) >= now();Здесь для каждой строки нужно вычислить выражение поверх
created_at. Индексу сложнее помочь, потому что сравнивается уже не сама колонка, а результат вычисления.Часто условие можно переписать так:
SELECT * FROM orders WHERE created_at >= now() - make_interval(days => 14);Теперь справа вычисляется граница диапазона, а слева остаётся обычная колонка. Такой запрос чаще дружит с индексом.
Принцип простой:
Отличия от других СУБД
Функции
make_timestamp,make_timestamptz,make_date,make_timeиmake_interval— это PostgreSQL.В других базах похожие задачи решаются иначе.
В MySQL для даты и времени часто используют функции вроде
MAKEDATE,MAKETIME,STR_TO_DATE, а интервалы задают через синтаксисINTERVAL.Например:
SELECT DATE_ADD(created_at, INTERVAL 10 DAY) AS expires_at FROM users;Для переменного количества дней в MySQL можно использовать выражение:
SELECT DATE_ADD(created_at, INTERVAL trial_days DAY) AS trial_ends_at FROM users;В ClickHouse есть свои функции для сборки даты и времени, например
makeDateTime.SELECT makeDateTime(2024, 3, 15, 14, 30, 0) AS ts;Для интервалов в ClickHouse используются функции вроде
toIntervalDay,toIntervalHourи похожие.SELECT created_at + toIntervalDay(10) AS expires_at FROM users;При переносе SQL между движками важно не копировать функции механически. Смысл похожий, но детали отличаются:
Если сборка даты влияет на биллинг, дедлайн, ключ отчёта или юридически важное время, такие места лучше проверять особенно внимательно.
Главное правило
Не склеивайте дату из строк без необходимости.
Если у вас уже есть отдельные числа — год, месяц, день, час, минута, секунды — используйте
make_timestamp,make_dateилиmake_time.Если вам нужно собрать длительность — используйте
make_interval.Если важен часовой пояс — используйте
make_timestamptzи явно указывайте зону.Такой код получается:
Для новичка это важная привычка: SQL работает не только со строками. У дат, времени и интервалов есть свои типы, и с ними лучше работать напрямую.