No PostgreSQL voce pode somar e subtrair datas e marcas de tempo como numeros, mas as regras dependem do tipo: um date nao se comporta como um timestamp. Entender essa diferenca evita os bugs classicos no calculo de vencimentos, tempo de casa e "ha quanto tempo" um evento aconteceu.
Somar dias a uma data
O caso mais comum e somar um inteiro a um date. O PostgreSQL trata isso como dias:
SELECT DATE '2024-03-01' + 7 AS next_week;
SELECT DATE '2024-03-01' - 30 AS month_ago;
E pratico para datas de vencimento: uma fatura costuma ser paga N dias depois de o pedido ser criado.
SELECT id,
created_at::date AS placed_on,
created_at::date + 14 AS due_date
FROM orders
WHERE status = 'pending';
- Somar um inteiro a um
date soma dias.
- Voce nao pode somar um inteiro a um
timestamp — isso e erro de tipo, e preciso um interval.
- Para "um mes depois" nao escreva
+ 30: os meses tem tamanhos diferentes, use INTERVAL '1 month'.
Diferenca de datas versus diferenca de timestamp
Aqui esta a pegadinha principal. Subtrair dois valores date resulta em um numero inteiro de dias, enquanto subtrair dois valores timestamp resulta em um interval.
SELECT DATE '2024-03-01' - DATE '2024-01-15' AS days;
SELECT TIMESTAMP '2024-03-01 09:00'
- TIMESTAMP '2024-01-15 18:30' AS span;
Por isso a mesma consulta se comporta de forma diferente conforme o tipo das colunas. Para garantir dias inteiros, converta os dois operandos para date:
SELECT id,
(NOW()::date - created_at::date) AS days_open
FROM orders
WHERE status = 'pending';
Pegadinha: NOW() - created_at sobre dois timestamp devolve um interval como 45 days 14:30:00, e compara-lo com > 30 falha com erro de tipo. Ou converte para date, ou compara contra um intervalo: NOW() - created_at > INTERVAL '30 days'.
Intervalos para timestamps
A um timestamp voce soma um interval, e nao um numero. Os intervalos respeitam o calendario: + INTERVAL '1 month' sobre 31 de janeiro cai em 28 (ou 29) de fevereiro, e nao em "30 dias depois".
SELECT created_at + INTERVAL '7 days' AS reminder_at,
created_at + INTERVAL '1 month' AS renew_at,
created_at + INTERVAL '36 hours' AS grace_until
FROM users;
Os intervalos somam e escalam, entao uma janela de "ultimos seis meses" fica compacta:
SELECT id, email
FROM users
WHERE created_at >= NOW() - INTERVAL '6 months';
Tempo de casa e idade em dias versus meses
Para o tempo de casa de um funcionario voce costuma querer dias brutos — entao subtraia valores date. Para uma idade legivel em anos e meses use AGE, que normaliza o intervalo pelo calendario.
SELECT name, dept,
NOW()::date - created_at::date AS tenure_days,
AGE(NOW(), created_at) AS tenure_human
FROM employees
ORDER BY tenure_days DESC;
Lembre-se: 365 dias nem sempre e "um ano". Se voce precisa de anos completos, calcule-os com AGE e EXTRACT(YEAR ...) em vez de dividir os dias por 365.
Diferencas em outros bancos
A sintaxe muda bastante entre os bancos:
- MySQL: subtrair datas nao devolve dias. Use
DATEDIFF(end, start) para dias e DATE_ADD(d, INTERVAL 7 DAY) / DATE_SUB para deslocar. Um simples d + 7 nao faz o que voce espera.
- ClickHouse: use
date + 7 (dias) e as funcoes dateDiff('day', start, end), addDays, addMonths; voce sempre informa a unidade de forma explicita.
Se o codigo precisa ser portavel, nao dependa de date - date: envolva a logica em DATEDIFF/dateDiff e use intervalos nomeados.
No PostgreSQL voce pode somar e subtrair datas e marcas de tempo como numeros, mas as regras dependem do tipo: um
datenao se comporta como umtimestamp. Entender essa diferenca evita os bugs classicos no calculo de vencimentos, tempo de casa e "ha quanto tempo" um evento aconteceu.Somar dias a uma data
O caso mais comum e somar um inteiro a um
date. O PostgreSQL trata isso como dias:SELECT DATE '2024-03-01' + 7 AS next_week; -- 2024-03-08 SELECT DATE '2024-03-01' - 30 AS month_ago; -- 2024-01-31E pratico para datas de vencimento: uma fatura costuma ser paga N dias depois de o pedido ser criado.
SELECT id, created_at::date AS placed_on, created_at::date + 14 AS due_date FROM orders WHERE status = 'pending';datesoma dias.timestamp— isso e erro de tipo, e preciso uminterval.+ 30: os meses tem tamanhos diferentes, useINTERVAL '1 month'.Diferenca de datas versus diferenca de timestamp
Aqui esta a pegadinha principal. Subtrair dois valores
dateresulta em um numero inteiro de dias, enquanto subtrair dois valorestimestampresulta em uminterval.SELECT DATE '2024-03-01' - DATE '2024-01-15' AS days; -- 46 (integer) SELECT TIMESTAMP '2024-03-01 09:00' - TIMESTAMP '2024-01-15 18:30' AS span; -- 45 days 14:30:00Por isso a mesma consulta se comporta de forma diferente conforme o tipo das colunas. Para garantir dias inteiros, converta os dois operandos para
date:SELECT id, (NOW()::date - created_at::date) AS days_open FROM orders WHERE status = 'pending';Intervalos para timestamps
A um
timestampvoce soma uminterval, e nao um numero. Os intervalos respeitam o calendario:+ INTERVAL '1 month'sobre 31 de janeiro cai em 28 (ou 29) de fevereiro, e nao em "30 dias depois".SELECT created_at + INTERVAL '7 days' AS reminder_at, created_at + INTERVAL '1 month' AS renew_at, created_at + INTERVAL '36 hours' AS grace_until FROM users;Os intervalos somam e escalam, entao uma janela de "ultimos seis meses" fica compacta:
SELECT id, email FROM users WHERE created_at >= NOW() - INTERVAL '6 months';Tempo de casa e idade em dias versus meses
Para o tempo de casa de um funcionario voce costuma querer dias brutos — entao subtraia valores
date. Para uma idade legivel em anos e meses useAGE, que normaliza o intervalo pelo calendario.SELECT name, dept, NOW()::date - created_at::date AS tenure_days, AGE(NOW(), created_at) AS tenure_human FROM employees ORDER BY tenure_days DESC;Lembre-se: 365 dias nem sempre e "um ano". Se voce precisa de anos completos, calcule-os com
AGEeEXTRACT(YEAR ...)em vez de dividir os dias por 365.Diferencas em outros bancos
A sintaxe muda bastante entre os bancos:
DATEDIFF(end, start)para dias eDATE_ADD(d, INTERVAL 7 DAY)/DATE_SUBpara deslocar. Um simplesd + 7nao faz o que voce espera.date + 7(dias) e as funcoesdateDiff('day', start, end),addDays,addMonths; voce sempre informa a unidade de forma explicita.Se o codigo precisa ser portavel, nao dependa de
date - date: envolva a logica emDATEDIFF/dateDiffe use intervalos nomeados.