Menu

Window Functions no SQLite: OVER e PARTITION BY

Entenda como as window functions funcionam no SQLite: OVER, PARTITION BY, ranking, LAG/LEAD e cláusulas de frame para totais acumulados.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

Window function: agrega uma coluna sem colapsar as linhas

O GROUP BY reduz várias linhas em uma só. Já uma window function faz algo diferente: ela calcula um valor sobre um conjunto de linhas relacionadas, mas mantém cada linha original no resultado. Você fica com o detalhe linha a linha e o valor agregado, lado a lado.

A estrutura é sempre a mesma: uma função, seguida de OVER (...).

A coluna total_all mostra o total geral de todas as linhas, repetido em cada uma. As linhas originais continuam intactas. Compare com SELECT SUM(amount) FROM sales — o número é o mesmo, mas a consulta devolve apenas uma linha. As window functions do SQLite te dão as duas visões ao mesmo tempo.

PARTITION BY: agregando dentro de grupos

Um OVER () vazio agrega sobre a tabela inteira. Adicionando PARTITION BY, você passa a agregar dentro de grupos, parecido com o GROUP BY — só que, de novo, sem colapsar as linhas.

Cada linha recebe o total da sua região e a fatia que representa nesse total. Com um GROUP BY tradicional, você perderia o detalhe por funcionário. Esse é o grande trunfo das window functions: detalhe e agregado na mesma query.

Ranking no SQLite: ROW_NUMBER, RANK e DENSE_RANK

A família de funções de ranking numera as linhas seguindo o ORDER BY que você define dentro do OVER. As três variações se diferenciam pela forma como lidam com empates.

Lendo o resultado:

  • ROW_NUMBER() é sempre único — empates são desfeitos de forma arbitrária. Use quando precisar de um número estável e distinto para cada linha.
  • RANK() atribui o mesmo rank a linhas empatadas e depois pula os números seguintes. Se dois jogadores empatam em 1, o próximo vem como rank 3.
  • DENSE_RANK() também trata empates, mas não pula. O próximo rank é 2.

Para fazer "top N por grupo", combine a função de ranking com PARTITION BY e filtre numa query externa — o WHERE não consegue referenciar window functions diretamente:

Os dois maiores faturamentos por região.

LAG e LEAD: olhando para as linhas vizinhas

LAG(col) devolve o valor de col da linha anterior dentro da window. Já o LEAD(col) faz o caminho contrário e olha para a linha seguinte. Os dois caem como uma luva quando você precisa comparar valores ao longo do tempo.

A primeira linha tem yesterday como NULL — afinal, não existe nada antes dela. Dá pra definir um valor padrão: LAG(celsius, 1, celsius) OVER (ORDER BY day) usa o valor de hoje quando não há linha anterior.

LEAD é o espelho do LAG. Juntando os dois com PARTITION BY, você consegue sequências por grupo — por exemplo, comparar as vendas deste mês com as do mês anterior dentro de cada região.

Total acumulado com window frames no SQLite

Coloque um ORDER BY dentro do OVER e funções de agregação como SUM, AVG e COUNT passam a calcular de forma cumulativa:

Dois detalhes importantes para reparar:

  • SUM(amount) OVER (ORDER BY day) calcula um total acumulado. Quando você escreve ORDER BY sem definir um frame explícito, o padrão é "do início da janela até a linha atual".
  • Já a segunda coluna usa um frame explícito: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW. Isso é uma janela deslizante de 3 linhas — ou seja, uma média móvel.

O modelo mental para entender frames é o seguinte: toda window function é avaliada sobre um frame de linhas, definido em relação à linha atual. Os frames mais comuns são:

  • ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW — total acumulado (é o padrão implícito).
  • ROWS BETWEEN N PRECEDING AND CURRENT ROW — janela retroativa (trailing).
  • ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING — a partição inteira.

ROWS conta linhas físicas. Existe também o RANGE, que agrupa por valor — útil quando há empates na coluna do ORDER BY e você quer que esses empates sejam tratados como um único passo.

FIRST_VALUE, LAST_VALUE e NTILE

Vale conhecer mais algumas window functions:

  • FIRST_VALUE e LAST_VALUE retornam o primeiro ou o último valor dentro do frame. No caso do LAST_VALUE, fique de olho no frame: por padrão ele termina em CURRENT ROW, então normalmente você vai querer usar ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING para pegar de fato o último valor da partição.
  • NTILE(n) divide as linhas em n grupos de tamanho aproximadamente igual — ótimo para quartis, percentis e divisões no estilo teste A/B.

Dando nome a uma janela com WINDOW

Quando várias colunas usam a mesma cláusula OVER (...), fica chato repetir tudo. O SQLite permite nomear uma janela uma única vez e reaproveitá-la:

Mesma consulta, com bem menos ruído. A cláusula WINDOW entra depois de WHERE/GROUP BY/HAVING e antes do ORDER BY.

Window function vs GROUP BY no SQLite

As duas envolvem agregação, mas resolvem problemas diferentes:

  • O GROUP BY reduz: gera uma linha por grupo. Use quando você só quer o resumo.
  • As window functions preservam: cada linha original continua ali, agora com colunas calculadas a mais ao lado.

Se você se pegar fazendo um GROUP BY e depois um join dos agregados de volta na tabela original, isso é um sinal claro de que uma window function resolveria tudo numa consulta só.

Algumas pegadinhas

  • O WHERE não enxerga window functions. Os filtros são aplicados antes do cálculo das janelas. A saída é envolver a consulta numa subquery ou CTE e filtrar no nível de fora.
  • Frames implícitos pegam todo mundo de surpresa. SUM(x) OVER (ORDER BY y) vira um total acumulado porque o frame padrão é RANGE UNBOUNDED PRECEDING. Se você quer a soma da partição inteira, escreva OVER (PARTITION BY ...) sem ORDER BY, ou defina o frame de forma explícita.
  • O LAST_VALUE confunde geral na primeira vez. Como o frame padrão termina na linha atual, ele devolve o valor da linha atual — e não o último da partição. Tem que sobrescrever o frame.
  • Window functions exigem SQLite 3.25+ (lançado em 2018). Qualquer instalação minimamente atual já tem suporte, mas alguns ambientes embarcados ficam para trás.

A seguir: colunas geradas

Window functions são computação na hora da consulta. A próxima página fala de computação na hora do armazenamento: as generated columns, em que o valor da coluna é definido por uma expressão e atualizado automaticamente conforme os dados mudam.

Perguntas frequentes

O que são window functions no SQLite?

As window functions calculam um valor sobre um conjunto de linhas relacionadas à linha atual, sem agrupar tudo como o GROUP BY faz. Você adiciona uma cláusula OVER (...) em funções como ROW_NUMBER(), RANK(), SUM() ou LAG() para definir a janela. Cada linha original continua no resultado — você só ganha uma coluna calculada a mais.

Qual a diferença entre RANK e DENSE_RANK no SQLite?

Os dois atribuem uma posição com base no ORDER BY, mas tratam empates de forma diferente. O RANK() deixa lacunas depois dos empates — se duas linhas empatam em 1º, a próxima vai para o 3º lugar. Já o DENSE_RANK() não faz isso: a próxima linha fica em 2º. Use DENSE_RANK() quando quiser posições consecutivas e RANK() quando o pulo fizer sentido.

Como calcular um total acumulado no SQLite?

Use SUM(coluna) OVER (ORDER BY ...) com uma window frame. Por padrão, um ORDER BY dentro do OVER aplica o frame RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, que já dá o total acumulado pronto. Se quiser reiniciar a soma por grupo, basta adicionar PARTITION BY.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR