Иногда дата приходит в базу не красивой строкой 2024-03-15, а по частям:
- отдельно год;
- отдельно месяц;
- отдельно день.
Так часто бывает после загрузки CSV, интеграции со старой ERP-системой, импорта из Excel или обработки веб-формы. В одной колонке лежит 2024, в другой 3, в третьей 15.
Можно было бы собрать строку вручную, а потом привести её к дате. Но это хрупкий путь: нужно думать о формате, нулях перед месяцем, разделителях и ошибках парсинга.
В PostgreSQL для таких случаев есть функции make_date и make_time.
Они собирают нормальные типизированные значения из готовых чисел:
make_date возвращает date;
make_time возвращает time.
Главная идея простая:
Если части даты или времени уже лежат в отдельных колонках, не склеивайте строку. Соберите значение напрямую.
Что делает make_date
Функция make_date принимает три числа:
make_date(year, month, day)
И возвращает дату.
SELECT make_date(2024, 3, 15) AS d;
Результат:
2024-03-15
Здесь всё читается буквально:
2024 — год;
3 — месяц;
15 — день.
Никаких строковых форматов вроде YYYY-MM-DD, никаких лишних CAST, никакой склейки через ||.
PostgreSQL сразу понимает, что вы хотите получить значение типа date.
Что делает make_time
Функция make_time работает похожим образом, но собирает время.
make_time(hour, min, sec)
Пример:
SELECT make_time(14, 30, 0) AS t;
Результат:
14:30:00
Аргументы означают:
14 — час;
30 — минута;
0 — секунда.
Секунды могут быть дробными. Это удобно, если в данных есть миллисекунды или доли секунды.
SELECT make_time(9, 5, 30.5) AS t;
Результат:
09:05:30.5
То есть не нужно собирать строку вроде 09:05:30.5. Можно передать числа напрямую.
Зачем это нужно
Представьте таблицу order_parts, куда попали данные из старой системы.
order_id | y | m | d
---------+------+---+----
101 | 2024 | 3 | 15
102 | 2024 | 3 | 16
103 | 2024 | 4 | 2
Год, месяц и день лежат отдельно.
С помощью make_date можно собрать полноценную дату:
SELECT
order_id,
make_date(y, m, d) AS order_date
FROM order_parts;
Результат будет таким:
order_id | order_date
---------+------------
101 | 2024-03-15
102 | 2024-03-16
103 | 2024-04-02
Это намного чище, чем собирать строку вручную:
(y || '-' || m || '-' || d)::date
Такой вариант сложнее читать и легче сломать. Особенно если месяц или день пришли без ведущего нуля.
make_date сразу говорит читателю запроса:
Мы собираем дату из года, месяца и дня.
Сборка даты из колонок
Самый полезный сценарий — когда части даты лежат в разных колонках.
Допустим, есть таблица order_parts.
SELECT
order_id,
make_date(y, m, d) AS order_date
FROM order_parts;
Такой запрос превращает три числовые колонки в одну нормальную дату.
Дальше с этой датой можно работать как обычно:
- сортировать;
- фильтровать;
- группировать;
- соединять с другими таблицами;
- сравнивать с другими датами.
Например, отсортируем заказы по собранной дате:
SELECT
order_id,
make_date(y, m, d) AS order_date
FROM order_parts
ORDER BY order_date;
Или сгруппируем строки по месяцу:
SELECT
date_trunc('month', make_date(y, m, d)) AS order_month,
count(*) AS orders_count
FROM order_parts
GROUP BY order_month
ORDER BY order_month;
Сначала мы собираем дату, а потом используем обычные инструменты PostgreSQL для работы с датами.
Фильтр по дате из параметров
make_date удобно использовать не только с колонками, но и с параметрами.
Например, приложение передало три числа:
- год
2024;
- месяц
3;
- день
15.
Нужно найти оплаченные заказы за этот день.
SELECT
id,
user_id,
amount
FROM orders
WHERE created_at::date = make_date(2024, 3, 15)
AND status = 'paid';
Запрос читается естественно:
Возьми дату из чисел 2024, 3, 15 и сравни с датой создания заказа.
Это понятнее, чем собирать строку и потом приводить её к типу date.
make_date проверяет корректность даты
Важное преимущество make_date: функция не делает вид, что всё хорошо, если дата неправильная.
Например, месяца 13 не существует.
SELECT make_date(2024, 13, 1);
PostgreSQL вернёт ошибку.
Дня 30 в феврале 2024 года тоже нет.
SELECT make_date(2024, 2, 30);
И это тоже ошибка.
Это хорошее поведение: грязные данные обнаруживаются сразу.
Если в импортированной таблице месяц 13, день 0 или 31 апреля, лучше узнать об этом на этапе обработки, а не получить тихо неправильный отчёт.
Почему ошибка — это и плюс, и минус
С одной стороны, ошибка защищает от мусора в данных.
С другой стороны, один битый ряд может уронить весь запрос.
Допустим, в таблице миллион строк, и только в одной строке месяц равен 13.
Такой запрос не вернёт «все хорошие строки». Он завершится ошибкой:
SELECT
order_id,
make_date(y, m, d) AS order_date
FROM order_parts;
Поэтому, если данные не вычищены, сначала лучше отфильтровать явно неправильные значения.
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;
Это базовая защита, но она не ловит все случаи. Например, она пропустит 2024-02-30, потому что день 30 сам по себе находится между 1 и 31.
Для более строгой проверки обычно используют отдельную валидацию данных на этапе загрузки или обработки.
Важная деталь про год
make_date принимает год как число.
Обычные годы работают ожидаемо:
SELECT make_date(2024, 1, 1) AS d;
Результат:
2024-01-01
Год 0 в PostgreSQL недопустим.
SELECT make_date(0, 1, 1);
Такой запрос вернёт ошибку.
А отрицательные годы относятся к датам до нашей эры. Например, -1 — это не ошибка, а год до нашей эры.
В обычных бизнес-таблицах такое почти не нужно, но полезно знать: если в данных случайно появился отрицательный год, PostgreSQL не всегда будет считать это ошибкой.
make_time для времени из отдельных частей
make_time полезен, когда отдельно хранятся час, минута и секунда.
Например, в настройках отдела указано начало рабочего дня:
dept | start_hour | start_minute
--------+------------+-------------
Sales | 9 | 0
Support | 10 | 30
QA | 11 | 0
Можно собрать нормальное значение time.
SELECT
dept,
make_time(start_hour, start_minute, 0) AS shift_start
FROM department_settings;
Результат:
dept | shift_start
--------+------------
Sales | 09:00:00
Support | 10:30:00
QA | 11:00:00
Теперь это не просто два числа, а полноценное время, с которым можно работать дальше.
Пример: единое время старта смены
Иногда время не хранится в таблице, а задаётся прямо в запросе.
Например, нужно показать отделы сотрудников и для каждого отдела указать начало смены в 09:00.
SELECT
e.dept,
count(*) AS people,
make_time(9, 0, 0) AS shift_start
FROM employees e
GROUP BY e.dept;
Здесь make_time(9, 0, 0) явно создаёт значение времени.
Можно было бы написать строку '09:00:00'::time, но make_time особенно хорош, когда час и минута приходят числами из параметров или другой таблицы.
Как собрать дату и время вместе
date и time можно сложить и получить timestamp.
Например, соберём дату 2024-03-15 и время 18:00:00.
SELECT
make_date(2024, 3, 15) + make_time(18, 0, 0) AS reminder_at;
Результат:
2024-03-15 18:00:00
Это удобно, когда дата и время приходят отдельно.
Например, дата напоминания хранится как год, месяц и день, а время — как час и минута.
SELECT
user_id,
make_date(y, m, d) + make_time(h, min, 0) AS reminder_at
FROM reminders_raw;
Так мы получаем полноценную метку времени без разбора строк.
make_timestamp и make_timestamptz
Если нужно сразу собрать полную дату и время, в PostgreSQL есть make_timestamp.
SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;
Результат:
2024-03-15 14:30:00
Сигнатура такая:
make_timestamp(year, month, day, hour, min, sec)
Если нужен вариант с часовым поясом, есть make_timestamptz.
SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Moscow') AS ts;
Такой вариант полезен, когда части даты и времени пришли отдельно, и вы точно знаете часовой пояс.
Например, пользователь выбрал в форме:
2024;
3;
15;
14;
30;
Europe/Moscow.
Тогда make_timestamptz собирает не просто локальное время, а конкретный момент времени с учётом зоны.
Почему лучше не собирать дату строкой
Иногда можно встретить такой подход:
SELECT
(y || '-' || m || '-' || d)::date AS order_date
FROM order_parts;
На первый взгляд всё работает.
Но у этого способа есть минусы:
- сложнее читать;
- нужно думать о формате строки;
- можно ошибиться с разделителями;
- одиночные цифры месяца и дня могут вести себя не так, как ожидается;
- парсинг строки обычно менее прозрачен, чем сборка типизированного значения.
make_date выражает намерение напрямую:
SELECT
make_date(y, m, d) AS order_date
FROM order_parts;
Этот запрос проще поддерживать. Даже новичок быстро понимает, что происходит.
Пример из жизни: импорт из CSV
Допустим, вы загрузили CSV от партнёра. В файле дата заказа разложена по трём колонкам.
order_id,year,month,day,amount
101,2024,3,15,5000
102,2024,3,16,3200
103,2024,4,2,7900
После загрузки в промежуточную таблицу можно собрать дату так:
SELECT
order_id,
make_date(year, month, day) AS order_date,
amount
FROM imported_orders;
А затем вставить данные в нормальную таблицу:
INSERT INTO orders (external_id, created_at, amount)
SELECT
order_id,
make_date(year, month, day),
amount
FROM imported_orders;
Так вы сразу приводите данные к нормальному типу date, а не тащите разрозненные части дальше по системе.
Пример: расписание из формы
Допустим, пользователь создаёт напоминание в интерфейсе.
Форма отправляет отдельные значения:
- год;
- месяц;
- день;
- час;
- минута.
В таблице reminder_input они лежат отдельно.
SELECT
user_id,
make_date(y, m, d) + make_time(h, min, 0) AS reminder_at
FROM reminder_input;
Получается полноценное значение:
2024-03-15 18:00:00
Такой подход хорошо подходит для учебных задач и для реальных ETL-пайплайнов: сначала принимаем данные как части, затем собираем нормальные типы.
Как аккуратно назвать результат
Хорошее имя колонки помогает читать запрос.
Не очень удачно:
SELECT
make_date(y, m, d) AS value
FROM order_parts;
Лучше:
SELECT
make_date(y, m, d) AS order_date
FROM order_parts;
Для времени:
SELECT
make_time(h, min, 0) AS shift_start
FROM shifts_raw;
Для полной метки времени:
SELECT
make_date(y, m, d) + make_time(h, min, 0) AS starts_at
FROM events_raw;
Когда речь идёт о датах и времени, названия особенно важны. Они помогают не спутать дату заказа, дату оплаты, дату доставки и дату загрузки данных.
Ошибки, о которых стоит помнить
Первая ошибка — передать аргументы не в том порядке.
Правильно:
SELECT make_date(2024, 3, 15) AS d;
Это 15 марта 2024.
Неправильно:
SELECT make_date(15, 3, 2024) AS d;
PostgreSQL попытается прочитать это как год 15, месяц 3, день 2024 и вернёт ошибку.
Вторая ошибка — думать, что make_date исправит плохую дату.
SELECT make_date(2024, 2, 30) AS d;
Такой даты нет, поэтому будет ошибка. Функция не переносит лишние дни в март.
Третья ошибка — проверять только диапазон 1..31 для дня и думать, что этого достаточно.
WHERE d BETWEEN 1 AND 31
Такой фильтр пропустит 2024-02-30, хотя это несуществующая дата.
Четвёртая ошибка — переносить запросы между СУБД по имени функции, не проверив сигнатуру. Особенно это касается MySQL.
MySQL: MAKEDATE работает иначе
В MySQL есть функция MAKEDATE, но она не является прямым аналогом PostgreSQL make_date.
В PostgreSQL:
make_date(year, month, day)
А в MySQL:
MAKEDATE(year, dayofyear)
То есть MySQL ждёт год и номер дня в году.
Например:
SELECT MAKEDATE(2024, 75) AS d;
Это дата, соответствующая 75-му дню 2024 года.
Если вам нужно собрать дату из года, месяца и дня в MySQL, часто используют STR_TO_DATE.
SELECT STR_TO_DATE('2024-3-15', '%Y-%c-%e') AS d;
Для времени в MySQL есть MAKETIME.
SELECT MAKETIME(14, 30, 0) AS t;
Главная ловушка: не переносите make_date(2024, 3, 15) из PostgreSQL в MySQL как MAKEDATE(2024, 3, 15). У функции другой смысл и другое количество аргументов.
ClickHouse: makeDate и makeDateTime
В ClickHouse есть функции, похожие по смыслу на PostgreSQL.
Для даты:
SELECT makeDate(2024, 3, 15) AS d;
Для даты и времени:
SELECT makeDateTime(2024, 3, 15, 14, 30, 0) AS ts;
Смысл близок к PostgreSQL: передаём год, месяц, день, час, минуту и секунду как отдельные числа.
Но при переносе всё равно нужно проверять поведение на плохих данных и граничных значениях. Разные СУБД могут по-разному реагировать на месяц 13, день 30 февраля или дату за пределами допустимого диапазона типа.
Как проверять перенос между СУБД
Если вы переносите запрос из PostgreSQL в MySQL или ClickHouse, не ограничивайтесь проверкой на красивой дате вроде 2024-03-15.
Обязательно проверьте граничные и плохие значения:
SELECT make_date(2024, 13, 1) AS d;
SELECT make_date(2024, 2, 30) AS d;
SELECT make_date(0, 1, 1) AS d;
В PostgreSQL такие случаи помогут быстро увидеть, где данные не проходят валидацию.
В другой СУБД результат может быть другим: ошибка, NULL, приведение к границе типа или совсем другая трактовка аргументов.
Главное правило при переносе:
Проверяйте не только имя функции, но и смысл каждого аргумента.
Когда использовать make_date и make_time
Используйте make_date, когда:
- год, месяц и день уже лежат в отдельных колонках;
- дата приходит из формы отдельными числами;
- данные загружены из CSV или старой системы;
- хочется избежать склейки строк;
- нужно сразу получить тип
date.
Используйте make_time, когда:
- час, минута и секунда приходят отдельно;
- время хранится в настройках;
- нужно собрать время из параметров;
- хочется получить тип
time, а не строку.
Используйте make_timestamp или сложение date + time, когда нужно получить полную дату со временем.
Главное из статьи
make_date и make_time в PostgreSQL собирают дату и время из отдельных числовых частей.
make_date принимает год, месяц и день:
SELECT make_date(2024, 3, 15) AS d;
make_time принимает час, минуту и секунду:
SELECT make_time(14, 30, 0) AS t;
Это удобнее и безопаснее, чем склеивать строку и потом приводить её к нужному типу.
make_date сразу проверяет корректность даты. Месяц 13, день 30 февраля и год 0 вызовут ошибку, а не превратятся тихо в другую дату.
Если дата и время лежат отдельно, их можно соединить:
SELECT make_date(2024, 3, 15) + make_time(18, 0, 0) AS reminder_at;
Для полной сборки timestamp есть make_timestamp, а для момента с часовым поясом — make_timestamptz.
При переносе между СУБД будьте особенно внимательны: MySQL MAKEDATE принимает год и день года, а не год, месяц и день. В ClickHouse есть похожие makeDate и makeDateTime, но поведение на плохих значениях нужно проверять отдельно.
Главная мысль простая: если дата или время уже разобраны на числа, собирайте их как типы, а не как строки. Так запросы становятся понятнее, надёжнее и приятнее для поддержки.
Иногда дата приходит в базу не красивой строкой
2024-03-15, а по частям:Так часто бывает после загрузки CSV, интеграции со старой ERP-системой, импорта из Excel или обработки веб-формы. В одной колонке лежит
2024, в другой3, в третьей15.Можно было бы собрать строку вручную, а потом привести её к дате. Но это хрупкий путь: нужно думать о формате, нулях перед месяцем, разделителях и ошибках парсинга.
В PostgreSQL для таких случаев есть функции
make_dateиmake_time.Они собирают нормальные типизированные значения из готовых чисел:
make_dateвозвращаетdate;make_timeвозвращаетtime.Главная идея простая:
Что делает make_date
Функция
make_dateпринимает три числа:make_date(year, month, day)И возвращает дату.
SELECT make_date(2024, 3, 15) AS d;Результат:
Здесь всё читается буквально:
2024— год;3— месяц;15— день.Никаких строковых форматов вроде
YYYY-MM-DD, никаких лишнихCAST, никакой склейки через||.PostgreSQL сразу понимает, что вы хотите получить значение типа
date.Что делает make_time
Функция
make_timeработает похожим образом, но собирает время.make_time(hour, min, sec)Пример:
SELECT make_time(14, 30, 0) AS t;Результат:
Аргументы означают:
14— час;30— минута;0— секунда.Секунды могут быть дробными. Это удобно, если в данных есть миллисекунды или доли секунды.
SELECT make_time(9, 5, 30.5) AS t;Результат:
То есть не нужно собирать строку вроде
09:05:30.5. Можно передать числа напрямую.Зачем это нужно
Представьте таблицу
order_parts, куда попали данные из старой системы.Год, месяц и день лежат отдельно.
С помощью
make_dateможно собрать полноценную дату:SELECT order_id, make_date(y, m, d) AS order_date FROM order_parts;Результат будет таким:
Это намного чище, чем собирать строку вручную:
(y || '-' || m || '-' || d)::dateТакой вариант сложнее читать и легче сломать. Особенно если месяц или день пришли без ведущего нуля.
make_dateсразу говорит читателю запроса:Сборка даты из колонок
Самый полезный сценарий — когда части даты лежат в разных колонках.
Допустим, есть таблица
order_parts.SELECT order_id, make_date(y, m, d) AS order_date FROM order_parts;Такой запрос превращает три числовые колонки в одну нормальную дату.
Дальше с этой датой можно работать как обычно:
Например, отсортируем заказы по собранной дате:
SELECT order_id, make_date(y, m, d) AS order_date FROM order_parts ORDER BY order_date;Или сгруппируем строки по месяцу:
SELECT date_trunc('month', make_date(y, m, d)) AS order_month, count(*) AS orders_count FROM order_parts GROUP BY order_month ORDER BY order_month;Сначала мы собираем дату, а потом используем обычные инструменты PostgreSQL для работы с датами.
Фильтр по дате из параметров
make_dateудобно использовать не только с колонками, но и с параметрами.Например, приложение передало три числа:
2024;3;15.Нужно найти оплаченные заказы за этот день.
SELECT id, user_id, amount FROM orders WHERE created_at::date = make_date(2024, 3, 15) AND status = 'paid';Запрос читается естественно:
Это понятнее, чем собирать строку и потом приводить её к типу
date.make_date проверяет корректность даты
Важное преимущество
make_date: функция не делает вид, что всё хорошо, если дата неправильная.Например, месяца
13не существует.SELECT make_date(2024, 13, 1);PostgreSQL вернёт ошибку.
Дня
30в феврале 2024 года тоже нет.SELECT make_date(2024, 2, 30);И это тоже ошибка.
Это хорошее поведение: грязные данные обнаруживаются сразу.
Если в импортированной таблице месяц
13, день0или31апреля, лучше узнать об этом на этапе обработки, а не получить тихо неправильный отчёт.Почему ошибка — это и плюс, и минус
С одной стороны, ошибка защищает от мусора в данных.
С другой стороны, один битый ряд может уронить весь запрос.
Допустим, в таблице миллион строк, и только в одной строке месяц равен
13.Такой запрос не вернёт «все хорошие строки». Он завершится ошибкой:
SELECT order_id, make_date(y, m, d) AS order_date FROM order_parts;Поэтому, если данные не вычищены, сначала лучше отфильтровать явно неправильные значения.
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;Это базовая защита, но она не ловит все случаи. Например, она пропустит
2024-02-30, потому что день30сам по себе находится между1и31.Для более строгой проверки обычно используют отдельную валидацию данных на этапе загрузки или обработки.
Важная деталь про год
make_dateпринимает год как число.Обычные годы работают ожидаемо:
SELECT make_date(2024, 1, 1) AS d;Результат:
Год
0в PostgreSQL недопустим.SELECT make_date(0, 1, 1);Такой запрос вернёт ошибку.
А отрицательные годы относятся к датам до нашей эры. Например,
-1— это не ошибка, а год до нашей эры.В обычных бизнес-таблицах такое почти не нужно, но полезно знать: если в данных случайно появился отрицательный год, PostgreSQL не всегда будет считать это ошибкой.
make_time для времени из отдельных частей
make_timeполезен, когда отдельно хранятся час, минута и секунда.Например, в настройках отдела указано начало рабочего дня:
Можно собрать нормальное значение
time.SELECT dept, make_time(start_hour, start_minute, 0) AS shift_start FROM department_settings;Результат:
Теперь это не просто два числа, а полноценное время, с которым можно работать дальше.
Пример: единое время старта смены
Иногда время не хранится в таблице, а задаётся прямо в запросе.
Например, нужно показать отделы сотрудников и для каждого отдела указать начало смены в
09:00.SELECT e.dept, count(*) AS people, make_time(9, 0, 0) AS shift_start FROM employees e GROUP BY e.dept;Здесь
make_time(9, 0, 0)явно создаёт значение времени.Можно было бы написать строку
'09:00:00'::time, ноmake_timeособенно хорош, когда час и минута приходят числами из параметров или другой таблицы.Как собрать дату и время вместе
dateиtimeможно сложить и получитьtimestamp.Например, соберём дату
2024-03-15и время18:00:00.SELECT make_date(2024, 3, 15) + make_time(18, 0, 0) AS reminder_at;Результат:
Это удобно, когда дата и время приходят отдельно.
Например, дата напоминания хранится как год, месяц и день, а время — как час и минута.
SELECT user_id, make_date(y, m, d) + make_time(h, min, 0) AS reminder_at FROM reminders_raw;Так мы получаем полноценную метку времени без разбора строк.
make_timestamp и make_timestamptz
Если нужно сразу собрать полную дату и время, в PostgreSQL есть
make_timestamp.SELECT make_timestamp(2024, 3, 15, 14, 30, 0) AS ts;Результат:
Сигнатура такая:
make_timestamp(year, month, day, hour, min, sec)Если нужен вариант с часовым поясом, есть
make_timestamptz.SELECT make_timestamptz(2024, 3, 15, 14, 30, 0, 'Europe/Moscow') AS ts;Такой вариант полезен, когда части даты и времени пришли отдельно, и вы точно знаете часовой пояс.
Например, пользователь выбрал в форме:
2024;3;15;14;30;Europe/Moscow.Тогда
make_timestamptzсобирает не просто локальное время, а конкретный момент времени с учётом зоны.Почему лучше не собирать дату строкой
Иногда можно встретить такой подход:
SELECT (y || '-' || m || '-' || d)::date AS order_date FROM order_parts;На первый взгляд всё работает.
Но у этого способа есть минусы:
make_dateвыражает намерение напрямую:SELECT make_date(y, m, d) AS order_date FROM order_parts;Этот запрос проще поддерживать. Даже новичок быстро понимает, что происходит.
Пример из жизни: импорт из CSV
Допустим, вы загрузили CSV от партнёра. В файле дата заказа разложена по трём колонкам.
После загрузки в промежуточную таблицу можно собрать дату так:
SELECT order_id, make_date(year, month, day) AS order_date, amount FROM imported_orders;А затем вставить данные в нормальную таблицу:
INSERT INTO orders (external_id, created_at, amount) SELECT order_id, make_date(year, month, day), amount FROM imported_orders;Так вы сразу приводите данные к нормальному типу
date, а не тащите разрозненные части дальше по системе.Пример: расписание из формы
Допустим, пользователь создаёт напоминание в интерфейсе.
Форма отправляет отдельные значения:
В таблице
reminder_inputони лежат отдельно.SELECT user_id, make_date(y, m, d) + make_time(h, min, 0) AS reminder_at FROM reminder_input;Получается полноценное значение:
Такой подход хорошо подходит для учебных задач и для реальных ETL-пайплайнов: сначала принимаем данные как части, затем собираем нормальные типы.
Как аккуратно назвать результат
Хорошее имя колонки помогает читать запрос.
Не очень удачно:
SELECT make_date(y, m, d) AS value FROM order_parts;Лучше:
SELECT make_date(y, m, d) AS order_date FROM order_parts;Для времени:
SELECT make_time(h, min, 0) AS shift_start FROM shifts_raw;Для полной метки времени:
SELECT make_date(y, m, d) + make_time(h, min, 0) AS starts_at FROM events_raw;Когда речь идёт о датах и времени, названия особенно важны. Они помогают не спутать дату заказа, дату оплаты, дату доставки и дату загрузки данных.
Ошибки, о которых стоит помнить
Первая ошибка — передать аргументы не в том порядке.
Правильно:
SELECT make_date(2024, 3, 15) AS d;Это
15 марта 2024.Неправильно:
SELECT make_date(15, 3, 2024) AS d;PostgreSQL попытается прочитать это как год
15, месяц3, день2024и вернёт ошибку.Вторая ошибка — думать, что
make_dateисправит плохую дату.SELECT make_date(2024, 2, 30) AS d;Такой даты нет, поэтому будет ошибка. Функция не переносит лишние дни в март.
Третья ошибка — проверять только диапазон
1..31для дня и думать, что этого достаточно.WHERE d BETWEEN 1 AND 31Такой фильтр пропустит
2024-02-30, хотя это несуществующая дата.Четвёртая ошибка — переносить запросы между СУБД по имени функции, не проверив сигнатуру. Особенно это касается MySQL.
MySQL: MAKEDATE работает иначе
В MySQL есть функция
MAKEDATE, но она не является прямым аналогом PostgreSQLmake_date.В PostgreSQL:
make_date(year, month, day)А в MySQL:
MAKEDATE(year, dayofyear)То есть MySQL ждёт год и номер дня в году.
Например:
SELECT MAKEDATE(2024, 75) AS d;Это дата, соответствующая 75-му дню 2024 года.
Если вам нужно собрать дату из года, месяца и дня в MySQL, часто используют
STR_TO_DATE.SELECT STR_TO_DATE('2024-3-15', '%Y-%c-%e') AS d;Для времени в MySQL есть
MAKETIME.SELECT MAKETIME(14, 30, 0) AS t;Главная ловушка: не переносите
make_date(2024, 3, 15)из PostgreSQL в MySQL какMAKEDATE(2024, 3, 15). У функции другой смысл и другое количество аргументов.ClickHouse: makeDate и makeDateTime
В ClickHouse есть функции, похожие по смыслу на PostgreSQL.
Для даты:
SELECT makeDate(2024, 3, 15) AS d;Для даты и времени:
SELECT makeDateTime(2024, 3, 15, 14, 30, 0) AS ts;Смысл близок к PostgreSQL: передаём год, месяц, день, час, минуту и секунду как отдельные числа.
Но при переносе всё равно нужно проверять поведение на плохих данных и граничных значениях. Разные СУБД могут по-разному реагировать на месяц
13, день30февраля или дату за пределами допустимого диапазона типа.Как проверять перенос между СУБД
Если вы переносите запрос из PostgreSQL в MySQL или ClickHouse, не ограничивайтесь проверкой на красивой дате вроде
2024-03-15.Обязательно проверьте граничные и плохие значения:
SELECT make_date(2024, 13, 1) AS d;SELECT make_date(2024, 2, 30) AS d;SELECT make_date(0, 1, 1) AS d;В PostgreSQL такие случаи помогут быстро увидеть, где данные не проходят валидацию.
В другой СУБД результат может быть другим: ошибка,
NULL, приведение к границе типа или совсем другая трактовка аргументов.Главное правило при переносе:
Когда использовать make_date и make_time
Используйте
make_date, когда:date.Используйте
make_time, когда:time, а не строку.Используйте
make_timestampили сложениеdate + time, когда нужно получить полную дату со временем.Главное из статьи
make_dateиmake_timeв PostgreSQL собирают дату и время из отдельных числовых частей.make_dateпринимает год, месяц и день:SELECT make_date(2024, 3, 15) AS d;make_timeпринимает час, минуту и секунду:SELECT make_time(14, 30, 0) AS t;Это удобнее и безопаснее, чем склеивать строку и потом приводить её к нужному типу.
make_dateсразу проверяет корректность даты. Месяц13, день30февраля и год0вызовут ошибку, а не превратятся тихо в другую дату.Если дата и время лежат отдельно, их можно соединить:
SELECT make_date(2024, 3, 15) + make_time(18, 0, 0) AS reminder_at;Для полной сборки timestamp есть
make_timestamp, а для момента с часовым поясом —make_timestamptz.При переносе между СУБД будьте особенно внимательны: MySQL
MAKEDATEпринимает год и день года, а не год, месяц и день. В ClickHouse есть похожиеmakeDateиmakeDateTime, но поведение на плохих значениях нужно проверять отдельно.Главная мысль простая: если дата или время уже разобраны на числа, собирайте их как типы, а не как строки. Так запросы становятся понятнее, надёжнее и приятнее для поддержки.