No PostgreSQL a funcao AGE responde "quanto tempo se passou" do jeito que uma pessoa entende: em anos, meses e dias, e nao como uma contagem bruta de dias. Por isso ela e a ferramenta natural para a idade de usuarios, o tempo de casa de funcionarios e o quao antigo e um pedido.
Dois argumentos contra um
AGE tem duas formas. Com dois argumentos ela calcula o intervalo entre dois momentos, e a ordem e AGE(end_ts, start_ts) — fim menos inicio.
SELECT AGE(TIMESTAMP '2024-03-01', TIMESTAMP '2021-11-15') AS gap;
Com um unico argumento o ponto de referencia passa a ser current_date de forma implicita (meia-noite de hoje), entao o resultado muda a cada dia:
SELECT AGE(TIMESTAMP '1990-06-17') AS since_birth;
Compare com a subtracao simples. O operador - sobre duas datas devolve apenas um numero de dias — util, mas nunca responde "quantos anos".
SELECT DATE '2024-03-01' - DATE '2021-11-15' AS raw_days;
Idade de usuarios e antiguidade de pedidos
Vamos usar nossas tabelas. Para saber ha quanto tempo um usuario tem conta, passe created_at como argumento unico:
SELECT id, email, AGE(created_at) AS account_age
FROM users
ORDER BY created_at;
A antiguidade de um pedido funciona do mesmo jeito. E pratico filtrar pelo intervalo direto — o PostgreSQL compara um interval com um literal:
SELECT o.id, o.amount, AGE(o.created_at) AS order_age
FROM orders o
WHERE o.status = 'paid'
AND AGE(o.created_at) > INTERVAL '90 days';
Para funcionarios AGE descreve o tempo de casa com clareza quando ha uma data de admissao (aqui created_at faz o papel de ponto de partida):
SELECT name, dept, AGE(NOW(), created_at) AS tenure
FROM employees
ORDER BY tenure DESC;
Por que os meses dependem do calendario
O traco principal do interval que AGE devolve: os meses sao contados pelo calendario, e nao como 30 dias fixos. O PostgreSQL avanca a data primeiro por anos inteiros, depois por meses inteiros, e so o resto e expresso em dias.
SELECT AGE(DATE '2024-03-31', DATE '2024-01-31') AS feb_gap;
Por causa disso, o mesmo periodo em dias pode dar um numero de meses diferente — fevereiro e mais curto que julho. Isso e correto para uma "idade", mas vira fonte de surpresas se voce esperava aritmetica em dias.
AGE sempre normaliza o resultado em anos, meses e dias.
- Meses de calendario nao tem 30 dias — a duracao depende das datas reais.
- Para uma contagem exata de dias use a subtracao
date - date ou EXTRACT(EPOCH ...).
O intervalo e impresso de forma legivel, mas para comparar e agrupar voce precisa de um numero. EXTRACT retira um campo do intervalo:
SELECT id, email,
EXTRACT(YEAR FROM AGE(created_at))::int AS full_years
FROM users;
Pegadinha: EXTRACT(YEAR FROM AGE(...)) devolve apenas os anos do intervalo e descarta os meses. Para um usuario com "2 anos e 11 meses" voce obtem 2, e nao um 3 arredondado. Se voce quer anos completos, e exatamente isso; se quer o total de meses, calcule years * 12 + months.
SELECT id,
EXTRACT(YEAR FROM AGE(created_at)) * 12
+ EXTRACT(MONTH FROM AGE(created_at)) AS total_months
FROM users;
Diferencas em outros bancos
AGE e uma extensao do PostgreSQL sem equivalente direto no padrao.
- MySQL: use
TIMESTAMPDIFF(YEAR, start, end) para anos inteiros ou DATEDIFF para dias. Nao ha um intervalo anos-meses-dias pronto.
- ClickHouse: oferece
age('year', start, end) e dateDiff('day', ...); voce sempre informa a unidade de forma explicita, e tambem nao ha intervalo simbolico.
Se o codigo precisa ser portavel, calcule os anos com TIMESTAMPDIFF/dateDiff, e construa o intervalo legivel apenas no PostgreSQL.
No PostgreSQL a funcao
AGEresponde "quanto tempo se passou" do jeito que uma pessoa entende: em anos, meses e dias, e nao como uma contagem bruta de dias. Por isso ela e a ferramenta natural para a idade de usuarios, o tempo de casa de funcionarios e o quao antigo e um pedido.Dois argumentos contra um
AGEtem duas formas. Com dois argumentos ela calcula o intervalo entre dois momentos, e a ordem eAGE(end_ts, start_ts)— fim menos inicio.SELECT AGE(TIMESTAMP '2024-03-01', TIMESTAMP '2021-11-15') AS gap; -- 2 years 3 mons 14 daysCom um unico argumento o ponto de referencia passa a ser
current_datede forma implicita (meia-noite de hoje), entao o resultado muda a cada dia:SELECT AGE(TIMESTAMP '1990-06-17') AS since_birth; -- evaluated against midnight todayCompare com a subtracao simples. O operador
-sobre duas datas devolve apenas um numero de dias — util, mas nunca responde "quantos anos".SELECT DATE '2024-03-01' - DATE '2021-11-15' AS raw_days; -- 837Idade de usuarios e antiguidade de pedidos
Vamos usar nossas tabelas. Para saber ha quanto tempo um usuario tem conta, passe
created_atcomo argumento unico:SELECT id, email, AGE(created_at) AS account_age FROM users ORDER BY created_at;A antiguidade de um pedido funciona do mesmo jeito. E pratico filtrar pelo intervalo direto — o PostgreSQL compara um
intervalcom um literal:SELECT o.id, o.amount, AGE(o.created_at) AS order_age FROM orders o WHERE o.status = 'paid' AND AGE(o.created_at) > INTERVAL '90 days';Para funcionarios
AGEdescreve o tempo de casa com clareza quando ha uma data de admissao (aquicreated_atfaz o papel de ponto de partida):SELECT name, dept, AGE(NOW(), created_at) AS tenure FROM employees ORDER BY tenure DESC;Por que os meses dependem do calendario
O traco principal do
intervalqueAGEdevolve: os meses sao contados pelo calendario, e nao como 30 dias fixos. O PostgreSQL avanca a data primeiro por anos inteiros, depois por meses inteiros, e so o resto e expresso em dias.SELECT AGE(DATE '2024-03-31', DATE '2024-01-31') AS feb_gap; -- 2 mons (not 60 days, not 59)Por causa disso, o mesmo periodo em dias pode dar um numero de meses diferente — fevereiro e mais curto que julho. Isso e correto para uma "idade", mas vira fonte de surpresas se voce esperava aritmetica em dias.
AGEsempre normaliza o resultado em anos, meses e dias.date - dateouEXTRACT(EPOCH ...).Extraindo anos com EXTRACT
O intervalo e impresso de forma legivel, mas para comparar e agrupar voce precisa de um numero.
EXTRACTretira um campo do intervalo:SELECT id, email, EXTRACT(YEAR FROM AGE(created_at))::int AS full_years FROM users;SELECT id, EXTRACT(YEAR FROM AGE(created_at)) * 12 + EXTRACT(MONTH FROM AGE(created_at)) AS total_months FROM users;Diferencas em outros bancos
AGEe uma extensao do PostgreSQL sem equivalente direto no padrao.TIMESTAMPDIFF(YEAR, start, end)para anos inteiros ouDATEDIFFpara dias. Nao ha um intervalo anos-meses-dias pronto.age('year', start, end)edateDiff('day', ...); voce sempre informa a unidade de forma explicita, e tambem nao ha intervalo simbolico.Se o codigo precisa ser portavel, calcule os anos com
TIMESTAMPDIFF/dateDiff, e construa o intervalo legivel apenas no PostgreSQL.