Funções de janela em SQL, um exemplo por vez
A anatomia do OVER()
funcao(expr) OVER (
PARTITION BY <divide as linhas em grupos>
ORDER BY <ordena dentro de cada grupo>
ROWS/RANGE <quais linhas formam o frame>
)PARTITION BY é "por cliente / por dia / por dispositivo". ORDER BY dá sequência às linhas, e é isso que faz soma acumulada e LAG terem sentido. O frame define quantas linhas ao redor da atual entram no cálculo.
Deduplicação: o padrão que você usa toda semana
Feeds de CDC e eventos entregam a mesma chave várias vezes. Mantenha a última:
WITH ranqueado AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC
) AS rn
FROM raw.orders_stream
)
SELECT * FROM ranqueado WHERE rn = 1;
-- Atalho no Snowflake / BigQuery / Databricks:
SELECT * FROM raw.orders_stream
QUALIFY ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY updated_at DESC) = 1;Ranking: ROW_NUMBER vs RANK vs DENSE_RANK
SELECT jogador,
pontos,
ROW_NUMBER() OVER (ORDER BY pontos DESC) AS row_num,
RANK() OVER (ORDER BY pontos DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY pontos DESC) AS dense
FROM placar;
jogador | pontos | row_num | rnk | dense
--------+--------+---------+-----+------
ana | 980 | 1 | 1 | 1
bruno | 940 | 2 | 2 | 2
caio | 940 | 3 | 2 | 2
dora | 900 | 4 | 4 | 3Olhe o Caio: mesma pontuação do Bruno, três respostas diferentes. Essa distinção é pergunta clássica de entrevista.
Soma acumulada e média móvel
SELECT data,
valor,
SUM(valor) OVER (
ORDER BY data
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS receita_acumulada,
AVG(valor) OVER (
ORDER BY data
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS media_7d
FROM receita_diaria
ORDER BY data;Escreva o frame sempre. O padrão implícito (RANGE UNBOUNDED PRECEDING) junta valores empatados no ORDER BY e devolve, em silêncio, um número diferente do que a maioria espera.
LAG e LEAD: comparar uma linha com as vizinhas
Crescimento mês a mês por cliente e o próximo mês ativo:
SELECT customer_id,
mes,
receita,
LAG(receita) OVER (PARTITION BY customer_id ORDER BY mes) AS receita_anterior,
ROUND(100.0 * (receita - LAG(receita) OVER (PARTITION BY customer_id ORDER BY mes))
/ NULLIF(LAG(receita) OVER (PARTITION BY customer_id ORDER BY mes), 0), 1) AS var_pct,
LEAD(mes) OVER (PARTITION BY customer_id ORDER BY mes) AS proximo_mes_ativo
FROM receita_mensal_cliente;O NULLIF(..., 0) é o que evita a query morrer com divisão por zero no primeiro mês em que o cliente aparece.
Sessionização: o exemplo nível sênior
Agrupar eventos em sessões quando o intervalo passa de 30 minutos — LAG mais soma acumulada:
WITH intervalos AS (
SELECT user_id, event_at,
CASE WHEN event_at - LAG(event_at) OVER (PARTITION BY user_id ORDER BY event_at)
> INTERVAL '30 minutes'
THEN 1 ELSE 0 END AS nova_sessao
FROM eventos
)
SELECT user_id, event_at,
SUM(nova_sessao) OVER (
PARTITION BY user_id ORDER BY event_at
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS numero_sessao
FROM intervalos;Se você escreve isso do zero e explica, já passou da régua de SQL da maioria das empresas. Treinamos exatamente isso como exercício na trilha de SQL.
Três erros que o revisor sempre pega
- Filtrar a coluna da janela no WHERE — use uma CTE ou QUALIFY.
- Deixar o frame implícito e obter a soma acumulada errada com timestamps empatados.
- ORDER BY em coluna não única na deduplicação — inclua um critério de desempate para o resultado ser determinístico.
Perguntas frequentes
- O que é uma função de janela em SQL?
- É uma função que calcula um valor sobre um conjunto de linhas relacionadas à linha atual (a janela) sem agrupar essas linhas. Diferente do GROUP BY, toda linha de entrada continua na saída e ganha uma coluna calculada.
- Qual a diferença entre ROW_NUMBER, RANK e DENSE_RANK?
- Com empates: ROW_NUMBER dá 1,2,3,4 (único, mas arbitrário), RANK dá 1,2,2,4 (com buraco depois do empate) e DENSE_RANK dá 1,2,2,3 (sem buraco). Use ROW_NUMBER para deduplicar e RANK/DENSE_RANK para rankings.
- Como deduplicar linhas com função de janela?
- ROW_NUMBER() OVER (PARTITION BY a_chave_de_negocio ORDER BY updated_at DESC) dentro de uma CTE e depois filtrar WHERE rn = 1 na query externa. É o padrão 'última versão vence' em pipelines de ELT.
- PARTITION BY é o mesmo que GROUP BY?
- Não. PARTITION BY divide as linhas em grupos apenas para o cálculo da janela e mantém todas as linhas. GROUP BY colapsa cada grupo em uma linha só. Se você precisa do detalhe e do agregado lado a lado, é PARTITION BY.
- Posso usar função de janela no WHERE?
- Não — a janela é avaliada depois do WHERE. Envolva a query em uma CTE ou subquery e filtre a coluna da janela por fora. No Snowflake, BigQuery e Databricks você também pode usar QUALIFY.
- Como fazer soma acumulada em SQL?
- SUM(valor) OVER (ORDER BY data ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). Escreva sempre o frame de forma explícita — o frame padrão muda o resultado quando existem valores empatados no ORDER BY.
Pronto para assinar?
7 dias grátis. Depois, menos que um café por mês — cancele quando quiser.
Assinar — 7 dias grátis- Sem cartão no teste grátis
- Cancele quando quiser
- 300+ exercícios
- 14 cursos completos