Fundamentos de SQL

Funções de janela em SQL, um exemplo por vez

Função de janela é a habilidade de SQL com maior retorno em dados — e o tema que a maioria das entrevistas usa para separar quem pratica de quem só leu tutorial. Todos os exemplos abaixo rodam em PostgreSQL, Snowflake, BigQuery, Databricks ou DuckDB.

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 |     3

Olhe 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