SQL Funções de Janela (OVER)

Com as funções agregadas e o GROUP BY, a Ana aprendeu a resumir: "a média de preço é tal", "cada categoria vendeu tanto". Só que o resumo engole as linhas: 7 produtos viram uma linha com a média. E se ela quiser ver cada produto e, ao lado, a média do grupo? Ou cada mês de vendas e, ao lado, quanto já foi vendido no ano até ali? Para isso existem as funções de janela: a mesma SUM, AVG ou COUNT de sempre, seguida da palavra OVER. Elas fazem a conta olhando para outras linhas, mas devolvem uma resposta para cada linha, sem juntar nada.

Você vai aprender
  • A diferença entre agregação (as linhas viram uma) e janela (as linhas ficam).
  • OVER (): a média, o total e a porcentagem ao lado de cada linha.
  • OVER (ORDER BY …): o total acumulado.
  • A moldura (frame) com ROWS BETWEEN: a média móvel.
  • A moldura padrão (RANGE … CURRENT ROW) e a surpresa quando há empates.
  • Janelas com nome (WINDOW) e onde uma janela pode e não pode aparecer.
  • Os erros mais comuns, com a mensagem real do SQLite.

Todos os exemplos rodam de verdade no seu navegador, no banco da Loja Aurora. O SQLite tem funções de janela desde a versão 3.25 (2018); o editor e o lab usam a 3.50.


A ideia: o boletim com a média da turma

Pense no boletim da escola. A secretaria pode fazer duas coisas com as notas da turma:

  • Um cartaz no mural: "média da turma: 7,2". Uma linha só. Os alunos sumiram; sobrou o resumo. Isso é a agregação (AVG com ou sem GROUP BY).
  • O boletim de cada aluno, com a nota dele e, numa coluna ao lado, "média da turma: 7,2". Todo aluno continua lá, e cada um consegue se comparar com a turma. Isso é a janela.

O nome "janela" vem daí: cada linha olha pela janela e enxerga um conjunto de outras linhas (a turma inteira, ou só as anteriores a ela). A função calcula sobre o que a linha enxerga, e o resultado fica na própria linha.


1. Agregação × janela: as linhas não somem

Primeiro a agregação que você já conhece: a média de preço dos eletrônicos (categoria 2).

SELECT COUNT(*) AS produtos,
       ROUND(AVG(preco), 2) AS media
FROM produtos
WHERE categoria_id = 2;

Uma linha: 7 produtos, média de R$ 82,71. Agora a mesma média como função de janela:

SELECT nome,
       preco,
       ROUND(AVG(preco) OVER (), 2) AS media
FROM produtos
WHERE categoria_id = 2;
AVG(preco)
A mesma função de média de sempre (aula AVG()).
OVER
"Sobre". Transforma a agregação numa função de janela: "calcule a média sobre esta janela, mas sem juntar as linhas".
()
A definição da janela. Parênteses vazios querem dizer "a janela é todas as linhas do resultado". Os parênteses são obrigatórios, mesmo vazios.
WHERE categoria_id = 2
O WHERE roda antes da janela. Por isso a janela enxerga só os 7 eletrônicos, e a média é a deles, não a dos 45 produtos.

As 7 linhas continuaram lá, cada uma com o seu nome e preço, e a média (82.71) repetida ao lado de todas. Repare que não tem GROUP BY: com janela, você pode misturar a coluna nome com uma conta sobre o grupo, coisa que a agregação não deixa fazer direito.

Fazendo contas com a janela

Com a média ao lado de cada linha, dá para comparar. Quanto cada eletrônico está acima ou abaixo da média, e que fatia ele é da soma dos preços:

SELECT nome,
       preco,
       ROUND(preco - AVG(preco) OVER (), 2) AS diferenca,
       ROUND(100.0 * preco / SUM(preco) OVER (), 1) AS porcentagem
FROM produtos
WHERE categoria_id = 2
ORDER BY preco DESC;
preco - AVG(preco) OVER ()
O preço da linha menos a média de todas. Positivo: acima da média; negativo: abaixo.
SUM(preco) OVER ()
A soma dos 7 preços, repetida em cada linha.
100.0 * preco / SUM(…)
A porcentagem. O 100.0, com ponto, garante conta com decimais.
ORDER BY preco DESC
A ordem do resultado final. Ela não muda a conta da janela, que continua sendo sobre as 7 linhas.

A Caixa de Som Portátil está R$ 107,28 acima da média e sozinha é 32,8% da soma dos preços.


2. OVER (ORDER BY): o total acumulado

Agora o relatório que a Ana mais queria: as vendas de cada mês de 2025 e, ao lado, quanto já foi vendido no ano até aquele mês. Primeiro uma CTE (aula CTE (WITH)) monta as vendas de cada mês; depois a janela acumula:

WITH mensal AS (
  SELECT strftime('%Y-%m', p.data) AS mes,
         ROUND(SUM(i.quantidade * i.preco_unitario), 2) AS vendas
  FROM pedidos AS p
  JOIN itens_pedido AS i ON i.pedido_id = p.id
  WHERE p.data LIKE '2025-%'
  GROUP BY mes
)
SELECT mes,
       vendas,
       ROUND(SUM(vendas) OVER (ORDER BY mes), 2) AS acumulado
FROM mensal
ORDER BY mes;
mensal
A CTE: uma linha por mês de 2025, com o total vendido em produtos. strftime('%Y-%m', data) corta a data em "ano-mês" (aula Funções de Data).
SUM(vendas) OVER (ORDER BY mes)
Com um ORDER BY dentro do OVER, a janela de cada linha deixa de ser "todas as linhas" e passa a ser "da primeira até esta", na ordem dos meses. A soma dessa janela é o acumulado.
ROUND(…, 2)
Garante o resultado em centavos. Somar muitos decimais pode deixar caudas de ponto flutuante (aula Funções Numéricas), e o arredondamento evita surpresas.
ORDER BY mes (no fim)
A ordem de exibição. O ORDER BY de dentro do OVER diz como a janela anda; o de fora diz como o resultado aparece. São coisas diferentes: escreva os dois.

Leia a coluna acumulado de cima para baixo: em janeiro, 865.78 (só janeiro); em fevereiro, 865.78 + 401.7 = 1267.48; e assim por diante, até 5464.52 em dezembro, o total do ano. Cada linha enxerga só o passado e ela mesma.

Missão: a Ana quer acompanhar quanto já pagou de frete em 2026, pedido a pedido. Acrescente a coluna frete_acumulado: a soma do frete de todos os pedidos de 2026 até aquele, em ordem de data.

SELECT data, frete
FROM pedidos
WHERE data >= '2026-01-01'
ORDER BY data;
SELECT data,
       frete,
       SUM(frete) OVER (ORDER BY data) AS frete_acumulado
FROM pedidos
WHERE data >= '2026-01-01'
ORDER BY data;

3. A moldura: ROWS BETWEEN e a média móvel

Abril de 2025 foi um mês fraco (só R$ 25,13). Olhando mês a mês, os números pulam muito, e fica difícil ver a tendência. Uma média móvel resolve: em cada mês, a média dele e dos dois anteriores. Para isso você diz à janela exatamente quais linhas ela enxerga, com uma moldura (em inglês, frame):

WITH mensal AS (
  SELECT strftime('%Y-%m', p.data) AS mes,
         ROUND(SUM(i.quantidade * i.preco_unitario), 2) AS vendas
  FROM pedidos AS p
  JOIN itens_pedido AS i ON i.pedido_id = p.id
  WHERE p.data LIKE '2025-%'
  GROUP BY mes
)
SELECT mes,
       vendas,
       ROUND(AVG(vendas) OVER (
         ORDER BY mes
         ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
       ), 2) AS media_3_meses
FROM mensal
ORDER BY mes;
ORDER BY mes
Em que ordem as linhas ficam dentro da janela. Sem ordem, "as duas anteriores" não teria sentido.
ROWS
"Linhas". A moldura vai ser contada em linhas, uma a uma.
BETWEEN 2 PRECEDING
"Entre 2 anteriores": começa duas linhas antes da linha atual.
AND CURRENT ROW
"E a linha atual": termina nela. São três linhas: as duas de antes e a atual.

Repare nas primeiras linhas: em janeiro não há meses anteriores (dentro de 2025), então a média é só de janeiro (865.78); em fevereiro, de janeiro e fevereiro (633.74). Do março em diante, sempre três meses. E veja como a média móvel suaviza abril: 335.3, em vez de despencar para 25.13.

As peças da moldura

EscritaQuer dizer
UNBOUNDED PRECEDINGDesde a primeira linha ("anteriores, sem limite")
2 PRECEDINGDuas linhas antes da atual
CURRENT ROWA linha atual
1 FOLLOWINGUma linha depois da atual
UNBOUNDED FOLLOWINGAté a última linha ("seguintes, sem limite")

Combinações comuns: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW (acumulado), ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING (o mês, o anterior e o seguinte), ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING (o que falta até o fim do ano). Com uma ponta só, como ROWS 2 PRECEDING, a outra ponta é CURRENT ROW.

Experimente: troque a moldura e veja a coluna mudar. Tente ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING (média centrada) e ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING com SUM no lugar de AVG (quanto ainda faltava vender a partir daquele mês).

WITH mensal AS (
  SELECT strftime('%Y-%m', p.data) AS mes,
         ROUND(SUM(i.quantidade * i.preco_unitario), 2) AS vendas
  FROM pedidos AS p
  JOIN itens_pedido AS i ON i.pedido_id = p.id
  WHERE p.data LIKE '2025-%'
  GROUP BY mes
)
SELECT mes,
       vendas,
       ROUND(AVG(vendas) OVER (
         ORDER BY mes
         ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
       ), 2) AS janela
FROM mensal
ORDER BY mes;

4. A moldura padrão e a surpresa dos empates

No total acumulado da seção 2 você não escreveu moldura nenhuma, e funcionou. É que, quando o OVER tem ORDER BY e nenhuma moldura, vale uma moldura padrão:

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

Parece igual ao acumulado com ROWS, mas tem uma palavra diferente: RANGE ("faixa"). Com RANGE, a "linha atual" não é uma linha só: é a linha atual e todas as que empatam com ela no ORDER BY. As linhas empatadas se chamam pares (peers). Enquanto o ORDER BY não tem empate (cada mês aparece uma vez), RANGE e ROWS dão o mesmo resultado. Com empate, não.

Veja os pedidos do primeiro trimestre de 2026, numerados com um contador acumulado por mês:

SELECT id,
       data,
       strftime('%Y-%m', data) AS mes,
       COUNT(*) OVER (ORDER BY strftime('%Y-%m', data)) AS acumulado
FROM pedidos
WHERE data BETWEEN '2026-01-01' AND '2026-03-31'
ORDER BY data;

A Ana esperava 1, 2, 3, 4, 5, 6, 7. Veio 2, 2, 5, 5, 5, 7, 7. Os dois pedidos de janeiro empatam no ORDER BY (o mês é o mesmo), então são pares: a moldura de cada um vai "do começo até o último par", e os dois enxergam os dois. Por isso ambos mostram 2. Os três de fevereiro mostram 5 (2 de janeiro + 3 de fevereiro).

Isso não é um defeito: é a regra do padrão SQL, e às vezes é exatamente o que você quer ("quantos pedidos até o fim deste mês"). O problema é quando você não sabia. Há dois consertos:

SELECT id,
       strftime('%Y-%m', data) AS mes,
       COUNT(*) OVER (
         ORDER BY strftime('%Y-%m', data)
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS por_linha,
       COUNT(*) OVER (ORDER BY strftime('%Y-%m', data), id) AS com_desempate
FROM pedidos
WHERE data BETWEEN '2026-01-01' AND '2026-03-31'
ORDER BY id;
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
Moldura em linhas: até a linha atual, e só ela, sem os pares. O problema: entre dois pedidos do mesmo mês, quem vem primeiro é o SQLite que decide. Funciona, mas a ordem dentro do empate não é garantida.
ORDER BY strftime(…), id
Um desempate: dentro do mesmo mês, ordena pelo id. Sem empates, não há pares, e a moldura padrão anda uma linha por vez. É o conserto mais seguro: o resultado é o mesmo toda vez.
Regra prática

Num total acumulado, faça o ORDER BY da janela ser único (acrescente o id no fim, se preciso) ou escreva a moldura com ROWS. Assim os empates nunca pegam você de surpresa.


5. Uma janela com nome: WINDOW

Quando várias colunas usam a mesma janela, repetir OVER (ORDER BY mes ROWS …) em cada uma cansa. A cláusula WINDOW dá um nome à janela, e cada coluna só diz OVER nome:

WITH mensal AS (
  SELECT strftime('%Y-%m', p.data) AS mes,
         ROUND(SUM(i.quantidade * i.preco_unitario), 2) AS vendas
  FROM pedidos AS p
  JOIN itens_pedido AS i ON i.pedido_id = p.id
  WHERE p.data LIKE '2025-%'
  GROUP BY mes
)
SELECT mes,
       vendas,
       ROUND(SUM(vendas) OVER ate_aqui, 2) AS acumulado,
       COUNT(*) OVER ate_aqui AS meses,
       MAX(vendas) OVER ate_aqui AS melhor_ate_aqui
FROM mensal
WINDOW ate_aqui AS (ORDER BY mes ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
ORDER BY mes;
WINDOW ate_aqui AS ( … )
Define a janela uma vez, com nome. Vem depois do FROM/WHERE/GROUP BY/HAVING e antes do ORDER BY final.
OVER ate_aqui
Usa a janela pelo nome, sem parênteses.
MAX(vendas) OVER ate_aqui
Qualquer agregada vira janela: aqui, o melhor mês do ano até aquele ponto. Janeiro (865.78) segura o recorde o ano todo.

6. Onde a janela pode aparecer

A ordem em que o SQLite trabalha explica tudo: primeiro FROM e WHERE escolhem as linhas, depois GROUP BY/HAVING agrupam, depois as janelas são calculadas, e só então vêm o ORDER BY e o LIMIT finais. Por isso uma função de janela só pode aparecer na lista do SELECT e no ORDER BY. No WHERE ela ainda não existe.

Então como mostrar só os meses acima da média do ano? Calcule a janela numa CTE e filtre fora dela:

WITH mensal AS (
  SELECT strftime('%Y-%m', p.data) AS mes,
         ROUND(SUM(i.quantidade * i.preco_unitario), 2) AS vendas
  FROM pedidos AS p
  JOIN itens_pedido AS i ON i.pedido_id = p.id
  WHERE p.data LIKE '2025-%'
  GROUP BY mes
),
com_media AS (
  SELECT mes, vendas, ROUND(AVG(vendas) OVER (), 2) AS media
  FROM mensal
)
SELECT mes, vendas, media
FROM com_media
WHERE vendas > media;

Cinco meses de 2025 ficaram acima da média de R$ 455,38. A CTE com_media já tem a média pronta como coluna comum, e aí o WHERE funciona.

O que vem nas próximas aulas

Até aqui a janela foi sempre o resultado inteiro (ou o pedaço dele até a linha atual). Em PARTITION BY você vai dividir a janela em grupos, como "a média da categoria de cada produto". E existem funções que só funcionam como janela: ROW_NUMBER, RANK e DENSE_RANK numeram e fazem rankings; LAG e LEAD espiam a linha anterior e a seguinte.


7. Formas diferentes de fazer a mesma coisa

Antes das funções de janela, o total acumulado se fazia com uma subconsulta correlacionada (aula Subconsultas): para cada mês, somar os meses até ele.

WITH mensal AS (
  SELECT strftime('%Y-%m', p.data) AS mes,
         ROUND(SUM(i.quantidade * i.preco_unitario), 2) AS vendas
  FROM pedidos AS p
  JOIN itens_pedido AS i ON i.pedido_id = p.id
  WHERE p.data LIKE '2025-%'
  GROUP BY mes
)
SELECT m.mes,
       m.vendas,
       (SELECT ROUND(SUM(m2.vendas), 2)
        FROM mensal AS m2
        WHERE m2.mes <= m.mes) AS acumulado
FROM mensal AS m
ORDER BY m.mes;

O resultado é igual ao da seção 2. Mas a subconsulta roda uma vez para cada linha, e relê as linhas anteriores toda vez: com 12 meses não se nota; com 100 mil pedidos, faz diferença. E a média móvel de 3 meses, com subconsulta, fica bem mais difícil de escrever.

Você quer…Use
Um resumo, uma linha por grupoAgregação com GROUP BY
Cada linha e a média/total do conjunto ao ladoAVG(…) OVER (), SUM(…) OVER ()
Total acumuladoSUM(…) OVER (ORDER BY … ), com ORDER BY único ou ROWS
Média móvel, soma das últimas N linhasOVER (ORDER BY … ROWS BETWEEN N PRECEDING AND CURRENT ROW)
Num SQLite antigo (antes da 3.25), sem janelasSubconsulta correlacionada

Quando dá errado

Janela no WHERE
SELECT nome, preco
FROM produtos
WHERE preco > AVG(preco) OVER ();

A mensagem: misuse of window function AVG(), "uso indevido da função de janela AVG()". O WHERE roda antes das janelas existirem.

Como consertar: calcule a janela numa CTE e filtre no comando de fora, como na seção 6.

Filtrar pelo apelido da janela no HAVING
SELECT categoria_id, AVG(preco) OVER () AS media
FROM produtos
GROUP BY categoria_id
HAVING media > 50;

A mensagem: misuse of aliased window function media, "uso indevido da função de janela com apelido media". O HAVING também roda antes das janelas; o apelido aponta para uma conta que ainda não foi feita.

Como consertar: o mesmo da anterior: CTE primeiro, filtro depois.

Sem erro, mas errado: OVER sem parênteses

A Ana esqueceu os parênteses vazios:

SELECT nome, AVG(preco) OVER
FROM produtos;

O que acontece: nenhum erro, mas veio uma linha só, e a coluna se chama OVER! Sem os parênteses, o SQLite entendeu OVER como um apelido (como em AVG(preco) AS OVER), e o AVG virou uma agregação comum, que junta tudo numa linha. O nome que apareceu ao lado é o de um produto qualquer.

Como consertar: OVER (), com os parênteses, mesmo vazios. Se o cabeçalho de uma coluna aparecer como OVER, foi isso.

Moldura de trás para frente
SELECT nome,
       SUM(preco) OVER (ORDER BY preco
                        ROWS BETWEEN CURRENT ROW AND 2 PRECEDING) AS soma
FROM produtos;

A mensagem: unsupported frame specification, "especificação de moldura não suportada". O começo da moldura (CURRENT ROW) veio depois do fim (2 PRECEDING).

Como consertar: o que vem antes primeiro: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW.

RANGE com número e duas colunas no ORDER BY
SELECT nome,
       SUM(preco) OVER (ORDER BY categoria_id, preco
                        RANGE BETWEEN 2 PRECEDING AND CURRENT ROW) AS soma
FROM produtos;

A mensagem: RANGE with offset PRECEDING/FOLLOWING requires one ORDER BY expression, "RANGE com deslocamento PRECEDING/FOLLOWING exige uma expressão só no ORDER BY". RANGE 2 PRECEDING quer dizer "valores até 2 a menos que o atual", e isso só tem sentido com uma coluna para comparar.

Como consertar: se você queria "as 2 linhas anteriores", use ROWS. RANGE com número é para faixas de valor, com um ORDER BY de uma coluna só.

Contador acumulado que "pula" números

É a surpresa da seção 4: um COUNT(*) OVER (ORDER BY mes) que dá 2, 2, 5, 5, 5. Não há mensagem de erro; a causa é o empate no ORDER BY com a moldura padrão RANGE.

Como consertar: desempate o ORDER BY (ORDER BY mes, id) ou use ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.

Nos outros bancos
  • As funções de janela são do padrão SQL e existem com a mesma escrita no PostgreSQL, no SQL Server e no MySQL (a partir da 8.0). A moldura padrão com ORDER BY também é a mesma: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. A surpresa dos empates acontece em todos eles.
  • O PostgreSQL também só aceita janelas na lista do SELECT e no ORDER BY.
  • SQL Server: ROWS e RANGE existem desde a versão 2012, e o RANGE não aceita números (RANGE 2 PRECEDING não vale lá; só UNBOUNDED e CURRENT ROW). Para "as N linhas anteriores", use ROWS, que funciona igual em todos.

Referência rápida

EscritaO que faz
AVG(x) OVER ()A média de todas as linhas, ao lado de cada linha
SUM(x) OVER (ORDER BY y)Total acumulado na ordem de y (empates entram juntos)
… OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)Acumulado linha a linha, sem juntar empates
AVG(x) OVER (ORDER BY y ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)Média móvel de 3 linhas
… ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWINGA linha, a anterior e a seguinte
… ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWINGDa linha atual até o fim
WINDOW w AS (…) … OVER wJanela com nome, reaproveitada
Moldura padrão com ORDER BYRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
Moldura padrão sem ORDER BYTodas as linhas (todas são pares)
MensagemQuer dizer
misuse of window function AVG()Janela no WHERE (ou em outro lugar proibido)
misuse of aliased window function mediaO apelido de uma janela usado no HAVING
unsupported frame specificationMoldura com o começo depois do fim
RANGE with offset PRECEDING/FOLLOWING requires one ORDER BY expressionRANGE com número exige um ORDER BY de uma coluna
(coluna chamada OVER, uma linha só)Faltaram os parênteses: OVER ()

Exercícios

1. A palavra da janela

Complete para mostrar o preço de cada produto e, ao lado, o preço máximo de todos:

SELECT nome, preco, MAX(preco) AS maximo FROM produtos;

2. A média móvel

Complete a moldura para a média da linha atual e das três anteriores:

AVG(vendas) OVER (ORDER BY mes ROWS BETWEEN PRECEDING AND )

3. Qual é a diferença entre SELECT AVG(preco) FROM produtos e SELECT nome, AVG(preco) OVER () FROM produtos?

4. Um COUNT(*) OVER (ORDER BY mes) deu 2, 2, 5, 5, 5. Por quê?

5. A Ana escreveu WHERE vendas > AVG(vendas) OVER () e recebeu misuse of window function AVG(). O que ela deve fazer?

Missão final: quanto falta para o mais caro

Missão: para cada brinquedo (categoria 5), mostre nome, preco e a coluna falta: quanto falta para ele chegar ao preço do brinquedo mais caro, arredondado a 2 casas. Use MAX(preco) OVER ().

SELECT nome, preco
FROM produtos
WHERE categoria_id = 5;
SELECT nome,
       preco,
       ROUND(MAX(preco) OVER () - preco, 2) AS falta
FROM produtos
WHERE categoria_id = 5
ORDER BY falta;

O Jogo de Tabuleiro Trilha Mística é o mais caro, então a falta dele é 0.0. Repare que o WHERE deixou só os brinquedos, e o MAX da janela foi o deles, não o da loja inteira.


Resumo

  • Uma função de janela é uma conta sobre um conjunto de linhas que devolve a resposta em cada linha, sem juntar nada. Qualquer agregada vira janela com OVER.
  • OVER (): a janela é o resultado inteiro (depois do WHERE). Bom para média, total e porcentagem ao lado de cada linha.
  • OVER (ORDER BY …): a janela vai do começo até a linha atual, o total acumulado.
  • ROWS BETWEEN … AND … escolhe a moldura linha a linha: 2 PRECEDING AND CURRENT ROW é a média móvel de 3.
  • A moldura padrão é RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: linhas empatadas no ORDER BY entram juntas. Desempate (…, id) ou use ROWS.
  • Janelas só na lista do SELECT e no ORDER BY. Para filtrar por elas, CTE primeiro. E não esqueça os parênteses: OVER ().
  • Próxima aula: numerar e fazer rankings com ROW_NUMBER, RANK e DENSE_RANK.