🧠FFVAcademy
🪟

Window functions: ranking, running totals, lead/lag

15 min de leitura·+75 XP

Window functions são as ferramentas SQL mais poderosas que a maioria dos desenvolvedores não usa. Elas calculam valores baseados em linhas relacionadas sem colapsar o resultado — rank, running total, e variação período-a-período em uma única query limpa.

Sintaxe e componentes de uma window function

-- Sintaxe: função OVER (PARTITION BY ... ORDER BY ... frame)
-- PARTITION BY: divide em grupos (opcional)
-- ORDER BY: ordena dentro de cada partição
-- frame: define quais linhas a janela inclui

-- Exemplo base: funcionários com salário e rank no departamento
SELECT
    nome,
    departamento,
    salario,
    ROW_NUMBER() OVER (
        PARTITION BY departamento
        ORDER BY salario DESC
    ) AS rank_no_dept,
    RANK() OVER (
        PARTITION BY departamento
        ORDER BY salario DESC
    ) AS rank_com_empate,
    AVG(salario) OVER (
        PARTITION BY departamento
    ) AS media_dept,
    salario - AVG(salario) OVER (PARTITION BY departamento) AS diff_da_media
FROM funcionarios;

-- Top 3 vendedores por região (filtrar com subquery)
SELECT *
FROM (
    SELECT
        nome, regiao, vendas,
        RANK() OVER (PARTITION BY regiao ORDER BY vendas DESC) AS rk
    FROM vendedores
) ranked
WHERE rk <= 3;

Running totals, média móvel e FRAME

-- Running total (soma acumulada)
SELECT
    data,
    receita,
    SUM(receita) OVER (ORDER BY data) AS receita_acumulada,
    AVG(receita) OVER (
        ORDER BY data
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS media_movel_7d
FROM receitas_diarias
ORDER BY data;

-- Frames disponíveis:
-- ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW  ← padrão para SUM/AVG com ORDER BY
-- ROWS BETWEEN 6 PRECEDING AND CURRENT ROW          ← janela deslizante de 7 dias
-- ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING          ← linha anterior e posterior
-- ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ← toda a partição

-- Partição + running total: acumulado por produto por mês
SELECT
    produto,
    mes,
    vendas,
    SUM(vendas) OVER (
        PARTITION BY produto
        ORDER BY mes
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS acumulado_por_produto
FROM vendas_mensais
ORDER BY produto, mes;

LAG, LEAD, FIRST_VALUE, LAST_VALUE

-- LAG e LEAD: acessar linhas vizinhas
SELECT
    produto,
    mes,
    vendas,
    LAG(vendas) OVER (PARTITION BY produto ORDER BY mes) AS mes_anterior,
    vendas - LAG(vendas, 1, 0) OVER (
        PARTITION BY produto ORDER BY mes
    ) AS variacao_absoluta,
    ROUND(
        (vendas - LAG(vendas) OVER (PARTITION BY produto ORDER BY mes)) /
        NULLIF(LAG(vendas) OVER (PARTITION BY produto ORDER BY mes), 0) * 100, 2
    ) AS variacao_pct
FROM vendas_mensais
ORDER BY produto, mes;

-- FIRST_VALUE e LAST_VALUE: extremos da janela
SELECT
    vendedor,
    data_venda,
    valor,
    FIRST_VALUE(valor) OVER (
        PARTITION BY vendedor
        ORDER BY data_venda
    ) AS primeira_venda,
    LAST_VALUE(valor) OVER (
        PARTITION BY vendedor
        ORDER BY data_venda
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS ultima_venda   -- ⚠️ precisa do frame explícito!
FROM vendas
ORDER BY vendedor, data_venda;

-- NTH_VALUE, NTILE: percentis e quartis
SELECT
    produto, preco,
    NTILE(4) OVER (ORDER BY preco) AS quartil   -- divide em 4 grupos iguais
FROM produtos;

Casos de uso práticos

ProblemaSolução com window function
Primeiro pedido de cada clienteROW_NUMBER() OVER (PARTITION BY cliente ORDER BY data) = 1
Crescimento mês a mêsLAG(valor) OVER (PARTITION BY produto ORDER BY mes)
Média móvel 7 diasAVG() OVER (ORDER BY data ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
Top N por grupoRANK() OVER (PARTITION BY grupo ORDER BY valor DESC) <= N
% do total do grupoSUM() OVER (PARTITION BY grupo) no denominador
Valor acumuladoSUM() OVER (PARTITION BY ... ORDER BY data)
Quando usar window functions: sempre que você precisar de cálculos que dependem de outras linhas sem colapsar o resultado. Elas eliminam self-joins, subqueries correlacionadas e processamento em múltiplas passagens. O custo: uma única varredura da tabela com sorting — mais eficiente que alternativas procedurais.
💡
Próximo: Índices que funcionam — por que um índice pode não ser usado e como criar índices que realmente melhoram performance.
🧩

Quiz rápido

3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo