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 |
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çãoN PRECEDING— N linhas antes da linha atualCURRENT ROW— a linha atualN FOLLOWING— N linhas após a linha atualUNBOUNDED 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
- Indexe colunas usadas em ORDER BY — window functions ordenam dados internamente; índices ajudam
- Minimize partições — Um grande número de partições aumenta o custo de computação
- Reaproveite definições de janela — Muitos bancos suportam cláusulas
WINDOWpara evitar repetir a mesma especificação deOVER - Filtre depois da janela — window functions rodam antes do
WHERE, então use subconsultas ou CTEs para filtrar por ranking - 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
OVERmantê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!