SQL

LAG() e LEAD() no PostgreSQL: análise período a período sem self-joins

PG Monitoring Team May 24, 2026 7 min de leitura

Comparar cada linha com a anterior ou a próxima — a receita do mês passado, a leitura anterior, o próximo evento — é uma das necessidades mais comuns de análise. Antes das window functions, isso exigia self-joins horríveis. O LAG() e o LEAD() transformam isso em uma única passada legível sobre os dados.

O problema que resolvem

"Quanto a receita variou em relação ao mês passado?" Responder isso com SQL puro tradicionalmente significava juntar a tabela com ela mesma em mes = mes - 1 — verboso, lento e propenso a erros nas bordas. O LAG() e o LEAD() deixam cada linha espiar uma linha irmã da mesma partição sem nenhum join.

  • LAG(col, n, padrao) — valor de n linhas antes da atual (padrão n = 1).
  • LEAD(col, n, padrao) — valor de n linhas depois.

Sintaxe

LAG(coluna [, deslocamento [, valor_padrao]]) OVER (
  [PARTITION BY coluna_ou_expr]
  ORDER BY coluna_ou_expr   -- define o que é "anterior"
)

O ORDER BY é o que dá sentido a "anterior" e "próximo" — na prática, ele não é opcional. O PARTITION BY mantém o espiar dentro de cada grupo, então a primeira linha de um cliente nunca lê a última de outro.

Pegadinha: o ORDER BY dentro do OVER() é independente do ORDER BY externo da consulta. Se você ordenar a janela de um jeito e exibir de outro, seus deltas vão parecer embaralhados. Mantenha-os alinhados e adicione um desempate se a coluna de ordem tiver duplicatas.

Crescimento mês a mês

SELECT
  month,
  revenue,
  LAG(revenue) OVER (ORDER BY month) AS prev_revenue,
  revenue - LAG(revenue) OVER (ORDER BY month) AS delta,
  round(
    100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))
    / NULLIF(LAG(revenue) OVER (ORDER BY month), 0)
  , 1) AS pct_change
FROM monthly_revenue
ORDER BY month;

O NULLIF(..., 0) protege contra divisão por zero quando o mês anterior foi 0.

Valores padrão para as bordas

A primeira linha não tem antecessora, então o LAG() retorna NULL. Passe um terceiro argumento como valor padrão:

LAG(revenue, 1, 0) OVER (ORDER BY month)  -- a primeira linha recebe 0 em vez de NULL

Detectando lacunas em uma sequência

Encontre números de nota fiscal faltando — linhas em que o próximo número pula mais de 1:

SELECT invoice_no, next_no
FROM (
  SELECT
    invoice_no,
    LEAD(invoice_no) OVER (ORDER BY invoice_no) AS next_no
  FROM invoices
) s
WHERE next_no - invoice_no > 1;

Tempo entre eventos

Meça os segundos entre logins consecutivos por usuário — base para sessionização e detecção de anomalias:

SELECT
  user_id,
  logged_at,
  logged_at - LAG(logged_at) OVER (
    PARTITION BY user_id ORDER BY logged_at
  ) AS gap
FROM logins;

Por que vence o self-join

Um self-join relê a tabela e casa linhas por um predicado de junção — risco quadrático se as colunas não estiverem perfeitamente indexadas. O LAG()/LEAD() faz uma única passada ordenada (WindowAgg), então é mais rápido e mais difícil de errar.

Da análise ao alerta

O padrão de "lacuna entre eventos" acima é exatamente como o PG Monitoring detecta paradas silenciosas — um replication slot que para de avançar, um job que silenciosamente deixa de disparar. O mesmo raciocínio de window function que move seus relatórios move a detecção de anomalias em produção.

Related Articles

Ready to experience better PostgreSQL monitoring?

Join thousands of teams who switched from traditional tools to PG Monitoring's AI-powered platform.

Fale conosco