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ê escreveORDER BYsem 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_VALUEeLAST_VALUEretornam o primeiro ou o último valor dentro do frame. No caso doLAST_VALUE, fique de olho no frame: por padrão ele termina emCURRENT ROW, então normalmente você vai querer usarROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGpara pegar de fato o último valor da partição.NTILE(n)divide as linhas emngrupos 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 BYreduz: 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
WHEREnã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, escrevaOVER (PARTITION BY ...)semORDER BY, ou defina o frame de forma explícita. - O
LAST_VALUEconfunde 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.