sqlpostgresqlmake-datemake-time

make_date e make_time no SQL: montar datas e horas a partir de partes inteiras

Como montar uma data ou hora a partir de colunas inteiras separadas sem strings de formato e por que make_date valida partes fora do intervalo.

3 min de leituraReferencesql · postgresql · make-date · make-time · date-functions · mysql

make_date e make_time montam um valor a partir de numeros ja prontos: voce passa o ano, o mes e o dia (ou a hora, o minuto e o segundo) como inteiros separados, sem analisar uma string. Sao a ferramenta ideal quando as partes de uma data ja estao em colunas diferentes de um relatorio.

Sintaxe basica

make_date(year, month, day) retorna um date, e make_time(hour, min, sec) retorna um time. Os argumentos sao inteiros (os segundos sao double precision para guardar uma fracao). Nao ha strings de formato como 'YYYY-MM-DD' nem as surpresas de localidade que vem com elas.

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

Os segundos aceitam uma fracao, entao um momento com precisao de milissegundos e montado sem converter strings:

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

Um timestamp completo vem de make_timestamp(year, month, day, hour, min, sec), e a versao com fuso e make_timestamptz(...).

Montar uma data a partir de colunas separadas

O cenario principal e uma tabela onde o ano, o mes e o dia sao guardados como numeros em campos distintos. Suponha que uma carga de uma origem legada deixou as partes da data separadas:

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

make_date elimina a dor da concatenacao e do CAST: voce nao precisa montar a string y || '-' || m || '-' || d e torcer para que um mes de um digito nao quebre o formato. Os numeros entram na funcao como estao.

O mesmo truque e util para filtrar por uma data montada a partir de parametros. Todos os pedidos de um dia especifico, montados a partir de tres numeros:

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

Validacao de partes fora do intervalo

make_date nao fica calado diante de um absurdo: um valor fora do intervalo valido gera um erro em vez de avancar para o mes seguinte como faz a aritmetica de intervalos.

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

Isso e ao mesmo tempo uma vantagem e uma armadilha:

  • Vantagem: dados sujos (mes 13, dia 0) sao detectados na hora em vez de virarem em silencio uma data errada.
  • Pegadinha: uma unica linha quebrada em uma varredura grande derruba a consulta inteira. Se os dados nao estao limpos, valide as partes antes.

Proteja-se contra linhas ruins com um filtro previo:

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;

Um ano negativo e lido como antes de Cristo, entao make_date(-1, 1, 1) nao e um erro, mas 0001-01-01 BC. O ano zero e rejeitado.

Quando make_time compensa

make_time brilha quando a hora e o minuto vem de uma configuracao ou de um join. Por exemplo, normalizar a "janela de trabalho" de um departamento para um unico horario de inicio:

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

O time ja montado pode ser somado a uma data para obter um momento completo sem analisar strings:

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

Diferencas no MySQL e ClickHouse

Os nomes e o comportamento diferem do PostgreSQL.

  • MySQL nao tem make_date no sentido habitual. Tem MAKEDATE(year, dayofyear), que espera um ano e um dia do ano, nao um mes e um dia. Para montar a partir de mes e dia usa-se STR_TO_DATE, e para a hora usa-se 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 oferece makeDate(year, month, day) e makeDateTime(year, month, day, hour, minute, second), cujos nomes coincidem em sentido com o PostgreSQL.
SELECT
  makeDate(2024, 3, 15)                  AS d,
  makeDateTime(2024, 3, 15, 14, 30, 0)   AS t;

A diferenca chave ao portar: o MySQL MAKEDATE recebe um dia do ano, nao um mes e um dia, e engole sem reclamar uma entrada que o PostgreSQL rejeitaria. Sempre confira a assinatura antes de mover uma consulta entre bancos.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador