SQL window functions: Ranking, Totais Acumulados e Análises

Published: 2026-03-24

Window functions executam cálculos sobre um conjunto de linhas relacionadas à linha atual — sem colapsar o resultado em uma única linha de saída como o GROUP BY faz. Elas são um dos recursos mais poderosos do SQL moderno, com suporte em SQLite (3.25+), PostgreSQL, MySQL (8.0+), SQL Server e Oracle.

Dados de Exemplo

Usamos duas tabelas em todos os exemplos:

Tabela employees:

id name department hire_date salary
1 Alice Engineering 2023-01-15 95000
2 Bob Engineering 2023-06-01 85000
3 Carol Engineering 2024-03-10 85000
4 Dave Sales 2023-02-20 72000
5 Eve Sales 2023-09-05 68000
6 Frank Sales 2024-01-15 65000
7 Grace Marketing 2023-04-01 82000
8 Henry Marketing 2024-06-01 70000

Tabela monthly_sales:

id employee_id month revenue
1 1 2026-01 22000
2 1 2026-02 25000
3 1 2026-03 28000
4 2 2026-01 19000
5 2 2026-02 21000
6 2 2026-03 20000
7 3 2026-01 15000
8 3 2026-02 17000
9 3 2026-03 19000
10 4 2026-01 18000
11 4 2026-02 22000
12 4 2026-03 17000
13 5 2026-01 14000
14 5 2026-02 16000
15 5 2026-03 19000
16 6 2026-01 11000
17 6 2026-02 13000
18 6 2026-03 15000
19 7 2026-01 9000
20 7 2026-02 12000
21 7 2026-03 14000
22 8 2026-01 7000
23 8 2026-02 9000
24 8 2026-03 11000
Explore o conjunto de dados no DbGate Lite

Sintaxe de Window functions

Toda função de janela usa a cláusula OVER para definir sobre quais linhas operar:

function_name(...) OVER (
  [PARTITION BY column]
  [ORDER BY column]
  [frame_clause]
)
  • PARTITION BY — divide as linhas em grupos (como GROUP BY, mas mantém todas as linhas)
  • ORDER BY — define a ordenação das linhas dentro de cada partição
  • Frame clause — limita a janela a um intervalo de linhas relativo à linha atual

ROW_NUMBER()

ROW_NUMBER() atribui um número sequencial único a cada linha dentro de uma partição. A numeração sempre começa em 1, sem lacunas ou empates.

Exemplo: Numerar Todos os Funcionários por Salário

Exemplo: Numerar Funcionários Dentro de Cada Departamento

Usando PARTITION BY, a numeração recomeça em 1 para cada departamento:

RANK() e DENSE_RANK()

RANK() e DENSE_RANK() lidam com empates de forma diferente de ROW_NUMBER():

  • ROW_NUMBER() — sempre único, critério de desempate arbitrário
  • RANK() — mesmo valor recebe o mesmo ranking, depois pula números (1, 2, 2, 4)
  • DENSE_RANK() — mesmo valor recebe o mesmo ranking, sem lacunas (1, 2, 2, 3)

Exemplo: Comparar as Três Funções de Ranking

Bob e Carol ganham 85000 — repare como cada função trata o empate:

Exemplo: Ranqueamento de Funcionários Dentro do Departamento

NTILE()

NTILE(n) distribui as linhas em n grupos (baldes) aproximadamente iguais, numerados de 1 a n.

Exemplo: Dividir Funcionários em Quartis de Salário

Exemplo: Dividir Funcionários em Dois Níveis por Departamento

LAG() e LEAD()

LAG(column, offset) acessa um valor de uma linha anterior. LEAD(column, offset) acessa um valor de uma linha seguinte. Ambos têm offset padrão 1 e retornam NULL quando não há linha.

Exemplo: Comparação de Receita Mês a Mês

Compare a receita de cada mês com o mês anterior para cada funcionário:

Exemplo: Visualizar a Receita do Próximo Mês

Funções de Agregação como Window functions

Funções de agregação padrão (SUM, AVG, COUNT, MIN, MAX) podem ser usadas como window functions com OVER. Diferente de GROUP BY, cada linha é preservada na saída.

Exemplo: Salário de Cada Funcionário vs. Média do Departamento

Exemplo: Percentual do Salário em Relação ao Total

Calcule qual fração da folha de pagamento total cada funcionário representa:

Exemplo: Total Acumulado e Contagem Acumulada

Totais Acumulados por Partição

Combine PARTITION BY e ORDER BY para calcular totais acumulados dentro de grupos.

Exemplo: Receita Acumulada por Funcionário

Exemplo: Ranking de Receita Mensal Entre Todos os Funcionários

Descubra a posição de cada funcionário em cada mês:

Janelas (Window Frames)

Uma janela (frame) define exatamente quais linhas, relativas à linha atual, são incluídas no cálculo. A cláusula de frame vem depois de ORDER BY dentro de OVER.

Sintaxe

ROWS BETWEEN <start> AND <end>

Limites comuns:

  • UNBOUNDED PRECEDING — a partir da primeira linha da partição
  • N PRECEDING — N linhas antes da linha atual
  • CURRENT ROW — a linha atual
  • N FOLLOWING — N linhas após a linha atual
  • UNBOUNDED FOLLOWING — até a última linha da partição

Exemplo: Média Móvel de 2 Meses

Exemplo: Mínimo e Máximo de Receita Acumulados

FIRST_VALUE e LAST_VALUE

FIRST_VALUE(column) retorna o primeiro valor na janela. LAST_VALUE(column) retorna o último valor. Atenção: LAST_VALUE com o frame padrão (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) só enxerga até a linha atual.

Exemplo: Comparar Cada Mês com o Primeiro Mês

Exemplo: Comparar Cada Mês com o Melhor Mês

Use ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING para enxergar a partição inteira:

Exemplo Prático: Maior Salário por Departamento

Um padrão comum: usar uma função de janela em uma subconsulta e depois filtrar pelo ranking.

Exemplo: Funcionário com Maior Salário em Cada Departamento

Exemplo: Mês de Maior Receita por Funcionário

Window functions vs. GROUP BY

Uma diferença fundamental: GROUP BY colapsa linhas em linhas de resumo, enquanto window functions mantêm todas as linhas e adicionam uma coluna calculada.

Exemplo: Comparação Lado a Lado

GROUP BY retorna uma linha por departamento:

Window functions mantêm todas as linhas:

Casos de Uso Comuns para Window functions

  • Rankings e leaderboards — ROW_NUMBER, RANK, DENSE_RANK para consultas de top-N
  • Totais acumulados — SUM cumulativo para controle financeiro e de estoque
  • Comparação período a período — LAG/LEAD para análise mês a mês ou ano a ano
  • Médias móveis — Suavização de dados com AVG baseado em frame
  • Análise de percentis — NTILE para dividir dados em grupos iguais
  • Desduplicação — ROW_NUMBER para escolher uma linha por grupo
  • Análise de lacunas — LAG para detectar sequências faltantes ou mudanças incomuns

Dicas de Desempenho

  1. Indexe colunas usadas em ORDER BY — window functions ordenam dados internamente; índices ajudam
  2. Minimize partições — Um grande número de partições aumenta o custo de computação
  3. Reaproveite definições de janela — Muitos bancos suportam cláusulas WINDOW para evitar repetir a mesma especificação de OVER
  4. Filtre depois da janela — window functions rodam antes do WHERE, então use subconsultas ou CTEs para filtrar por ranking
  5. Evite frames desnecessários — Especifique cláusulas de frame apenas quando precisar de um comportamento diferente do padrão

Resumo

  • Window functions calculam valores sobre linhas relacionadas sem colapsar o conjunto de resultados
  • ROW_NUMBER, RANK, DENSE_RANK atribuem posições ordinais às linhas
  • LAG e LEAD acessam valores de linhas anteriores ou seguintes
  • NTILE distribui linhas em grupos de tamanho semelhante
  • FIRST_VALUE e LAST_VALUE retornam valores de borda da janela
  • Funções de agregação (SUM, AVG, COUNT, MIN, MAX) com OVER mantêm todas as linhas enquanto calculam métricas em nível de grupo
  • PARTITION BY cria grupos independentes; ORDER BY define a sequência das linhas
  • Janelas (frames) (ROWS BETWEEN ... AND ...) refinam quais linhas são incluídas
  • Use subconsultas para filtrar por resultados de window functions (por exemplo, top-N por grupo)
  • Suportadas em SQLite 3.25+, PostgreSQL, MySQL 8.0+, SQL Server e Oracle

Tente modificar os exemplos para experimentar diferentes partições, ordenações e cláusulas de frame e veja como as window functions podem simplificar suas consultas analíticas!