As funções de agregação básicas (SUM, AVG, COUNT) agrupam linhas e produzem um único valor por grupo. Este capítulo apresenta duas extensões poderosas: funções de janela e agregação multidimensional (ROLLUP, CUBE, GROUPING SETS).
Funções de Janela (Window Functions)
Uma função de janela calcula um valor para cada linha com base em um conjunto de linhas relacionadas — a janela — sem colapsar as linhas em grupos como GROUP BY faz. A cláusula OVER define a janela.
-- Professores ranqueados por salário (geral)SELECT nome, id_escola, salario,RANK() OVER (ORDERBY salario DESC) AS rank_geral,DENSE_RANK() OVER (ORDERBY salario DESC) AS dense_rank_geral,ROW_NUMBER() OVER (ORDERBY salario DESC, id_prof) AS row_numFROM professorORDERBY rank_geral;
7 records
nome
id_escola
salario
rank_geral
dense_rank_geral
row_num
Helena Carvalho
31
6700
1
1
1
Carla Pinto
11
6600
2
2
2
Bruno Teixeira
11
6500
3
3
3
Felipe Araujo
21
6400
4
4
4
Gustavo Costa
31
3300
5
5
5
Eduarda Souza
21
3200
6
6
6
Igor Melo
31
3100
7
7
7
Função
Comportamento com empates
RANK()
Posições iguais para empates; salta posições após
DENSE_RANK()
Posições iguais para empates; não salta posições
ROW_NUMBER()
Numera linhas sequencialmente; sem empates
Ranking por Partição
PARTITION BY reinicia o ranking dentro de cada grupo:
-- Ranking de salário dentro de cada escolaSELECT p.nome, e.sigla AS escola, p.salario,RANK() OVER (PARTITIONBY p.id_escolaORDERBY p.salario DESC) AS rank_na_escolaFROM professor pJOIN escola e ON e.id_escola = p.id_escolaORDERBY e.sigla, rank_na_escola;
7 records
nome
escola
salario
rank_na_escola
Felipe Araujo
EEMA
6400
1
Eduarda Souza
EEMA
3200
2
Helena Carvalho
ESETI
6700
1
Gustavo Costa
ESETI
3300
2
Igor Melo
ESETI
3100
3
Carla Pinto
ESG
6600
1
Bruno Teixeira
ESG
6500
2
NTILE — Dividindo em Quartis (ou N grupos)
NTILE(n) distribui as linhas em n grupos de tamanhos iguais (ou o mais próximo possível):
-- Distribuição dos alunos em quartis de nota (apenas notas lançadas)SELECT a.nome, md.nota,NTILE(4) OVER (ORDERBY md.nota DESCNULLSLAST) AS quartilFROM matricula_disciplina mdJOIN aluno a USING (id_aluno)WHERE md.nota ISNOTNULLORDERBY quartil, md.nota DESC;
Displaying records 1 - 10
nome
nota
quartil
Thiago Gomes
10.0
1
Camila Teixeira
9.9
1
Beatriz Carvalho
9.8
1
Carolina Lima
9.7
1
Carolina Lima
9.2
1
Matheus Silva
9.0
1
Larissa Araújo
9.0
1
Amanda Moreira
8.8
1
Beatriz Carvalho
8.8
1
Rafael Costa
8.7
1
Funções de Janela com Agregação
Funções de janela podem usar qualquer agregado (SUM, AVG, COUNT) com OVER:
-- Nota de cada aluno vs. média da turma (por disciplina/ano/semestre)SELECT d.nome_disciplina, a.nome AS aluno, md.nota,ROUND(AVG(md.nota) OVER (PARTITIONBY md.id_disciplina, md.ano, md.semestre ), 2) AS media_turma, md.nota -ROUND(AVG(md.nota) OVER (PARTITIONBY md.id_disciplina, md.ano, md.semestre ), 2) AS desvio_da_mediaFROM matricula_disciplina mdJOIN aluno a USING (id_aluno)JOIN disciplina d USING (id_disciplina)WHERE md.nota ISNOTNULLORDERBY d.nome_disciplina, md.nota DESC;
Displaying records 1 - 10
nome_disciplina
aluno
nota
media_turma
desvio_da_media
Bases da Eng. de Software 2
Matheus Silva
9.0
7.55
1.45
Bases da Eng. de Software 2
Larissa Araújo
6.1
7.55
-1.45
Bases da Eng. de Software 2
Diego Correia
6.1
4.00
2.10
Bases da Eng. de Software 2
Isabela Freitas
4.3
4.30
0.00
Bases da Eng. de Software 2
Juliana Nunes
1.9
4.00
-2.10
Bases da Eng. de Software 4
Larissa Araújo
9.0
8.25
0.75
Bases da Eng. de Software 4
Isabela Freitas
7.7
7.70
0.00
Bases da Eng. de Software 4
Matheus Silva
7.5
8.25
-0.75
Bases da Eng. de Software 6
Matheus Silva
4.1
2.20
1.90
Bases da Eng. de Software 6
Larissa Araújo
0.3
2.20
-1.90
Windowing — Janelas Deslizantes
A especificação ROWS BETWEEN define uma janela móvel de linhas relativas à linha atual:
-- Média acumulada de notas por aluno (ordenadas por semestre da disciplina)SELECT a.nome, d.semestre, d.nome_disciplina, md.nota,ROUND(AVG(md.nota) OVER (PARTITIONBY md.id_alunoORDERBY d.semestreROWSBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW ), 2) AS media_acumuladaFROM matricula_disciplina mdJOIN aluno a USING (id_aluno)JOIN disciplina d USING (id_disciplina)WHERE md.nota ISNOTNULLORDERBY a.nome, d.semestre;
Displaying records 1 - 10
nome
semestre
nome_disciplina
nota
media_acumulada
Amanda Moreira
2
Desenvolvimento Humano
8.4
8.40
Amanda Moreira
2
Didática Geral
8.2
8.30
Amanda Moreira
4
Eletiva I
8.5
8.37
Amanda Moreira
4
Fundamentos e Orientações Metodológicas - Ed. Infantil - BNCC
8.8
8.48
Amanda Moreira
6
Educação do Campo, Indígena e Quilombola
6.2
7.70
Amanda Moreira
6
Eletiva II
6.1
8.00
Beatriz Carvalho
2
História Econômica Geral
9.8
9.80
Beatriz Carvalho
2
Culturas Digitais
8.8
9.30
Camila Teixeira
2
História Econômica Geral
6.1
8.00
Camila Teixeira
2
Culturas Digitais
9.9
9.90
Especificações de janela comuns:
Especificação
Significado
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
Da primeira linha até a atual (acumulado)
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
Linha anterior, atual e próxima (média móvel de 3)
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
Da atual até a última
Agregação Multidimensional: ROLLUP e CUBE
O GROUP BY padrão gera um único nível de agrupamento. ROLLUP e CUBE geram múltiplos níveis de agrupamento em uma única consulta — equivalentes a várias consultas com GROUP BY e UNION ALL.
ROLLUP — Subtotais Hierárquicos
ROLLUP(A, B, C) gera a união de: (A, B, C), (A, B), (A), ()
-- Total de alunos por escola, por curso, e total geral (ROLLUP)SELECT e.sigla AS escola, c.sigla_curso AS curso,COUNT(a.id_aluno) AS total_alunosFROM escola eJOIN curso c ON c.id_escola = e.id_escolaLEFTJOIN aluno a ON a.id_curso = c.id_cursoGROUPBYROLLUP(e.sigla, c.sigla_curso)ORDERBY escola NULLSLAST, curso NULLSLAST;
7 records
escola
curso
total_alunos
EEMA
PED
6
EEMA
NA
6
ESETI
ENS
6
ESETI
NA
6
ESG
ECO
6
ESG
NA
6
NA
NA
18
As linhas com NULL em curso são os subtotais por escola; a linha com NULL em ambas é o total geral.
CUBE — Todos os Agrupamentos Possíveis
CUBE(A, B) gera a união de todos os 2² = 4 subconjuntos: (A, B), (A), (B), ().
-- Salário médio cruzando escola × carga horária semanal (CUBE)SELECT e.sigla AS escola, p.ch_semanal,ROUND(AVG(p.salario), 2) AS media_salarial,COUNT(*) AS professoresFROM professor pJOIN escola e ON e.id_escola = p.id_escolaGROUPBYCUBE(e.sigla, p.ch_semanal)ORDERBY escola NULLSLAST, ch_semanal NULLSLAST;
Displaying records 1 - 10
escola
ch_semanal
media_salarial
professores
EEMA
20
3200.00
1
EEMA
40
6400.00
1
EEMA
NA
4800.00
2
ESETI
20
3200.00
2
ESETI
40
6700.00
1
ESETI
NA
4366.67
3
ESG
40
6550.00
2
ESG
NA
6550.00
2
NA
20
3200.00
3
NA
40
6550.00
4
GROUPING() — Distinguindo NULL de Agregação vs. NULL de Dado
Quando um atributo tem NULL no resultado de ROLLUP/CUBE, é difícil saber se é um NULL genuíno do dado ou um NULL que representa “todos”. A função GROUPING() resolve isso:
-- GROUPING identifica linhas de subtotal (1 = agregado, 0 = valor real)SELECTCOALESCE(e.sigla, '*** TOTAL ***') AS escola,COALESCE(c.sigla_curso, '---') AS curso,COUNT(a.id_aluno) AS alunos,GROUPING(e.sigla) AS flag_escola,GROUPING(c.sigla_curso) AS flag_cursoFROM escola eJOIN curso c ON c.id_escola = e.id_escolaLEFTJOIN aluno a ON a.id_curso = c.id_cursoGROUPBYROLLUP(e.sigla, c.sigla_curso)ORDERBY flag_escola, flag_curso, escola, curso;
7 records
escola
curso
alunos
flag_escola
flag_curso
EEMA
PED
6
0
0
ESETI
ENS
6
0
0
ESG
ECO
6
0
0
EEMA
—
6
0
1
ESETI
—
6
0
1
ESG
—
6
0
1
*** TOTAL ***
—
18
1
1
GROUPING SETS — Controle Granular
GROUPING SETS permite especificar exatamente quais agrupamentos gerar, sem precisar dos padrões de ROLLUP ou CUBE:
-- Apenas os agrupamentos por escola e por carga horária (sem combinação)SELECT e.sigla AS escola, p.ch_semanal,COUNT(*) AS professores,ROUND(AVG(p.salario), 2) AS media_salarialFROM professor pJOIN escola e ON e.id_escola = p.id_escolaGROUPBYGROUPING SETS ( (e.sigla), -- total por escola (p.ch_semanal), -- total por carga horária () -- total geral)ORDERBY escola NULLSLAST, ch_semanal NULLSLAST;
6 records
escola
ch_semanal
professores
media_salarial
EEMA
NA
2
4800.00
ESETI
NA
3
4366.67
ESG
NA
2
6550.00
NA
20
3
3200.00
NA
40
4
6550.00
NA
NA
7
5114.29
Para Praticar
-- Top-2 alunos por nota em cada disciplina (usando RANK com PARTITION)WITH ranked AS (SELECT d.nome_disciplina, a.nome AS aluno, md.nota,RANK() OVER (PARTITIONBY md.id_disciplinaORDERBY md.nota DESCNULLSLAST ) AS rank_na_turmaFROM matricula_disciplina mdJOIN aluno a USING (id_aluno)JOIN disciplina d USING (id_disciplina)WHERE md.nota ISNOTNULL)SELECT nome_disciplina, aluno, nota, rank_na_turmaFROM rankedWHERE rank_na_turma <=2ORDERBY nome_disciplina, rank_na_turma;
Displaying records 1 - 10
nome_disciplina
aluno
nota
rank_na_turma
Bases da Eng. de Software 2
Matheus Silva
9.0
1
Bases da Eng. de Software 2
Diego Correia
6.1
2
Bases da Eng. de Software 2
Larissa Araújo
6.1
2
Bases da Eng. de Software 4
Larissa Araújo
9.0
1
Bases da Eng. de Software 4
Isabela Freitas
7.7
2
Bases da Eng. de Software 6
Matheus Silva
4.1
1
Bases da Eng. de Software 6
Larissa Araújo
0.3
2
Culturas Digitais
Camila Teixeira
9.9
1
Culturas Digitais
Beatriz Carvalho
8.8
2
Desenvolvimento Humano
Letícia Souza
8.4
1
-- Resumo executivo: total de alunos e média salarial por centro (ROLLUP)SELECTCOALESCE(ce.nome_centro, 'TOTAL GERAL') AS centro,COUNT(DISTINCT a.id_aluno) AS total_alunos,ROUND(AVG(p.salario), 2) AS media_salario_profFROM centro ceJOIN escola es ON es.id_centro = ce.id_centroLEFTJOIN curso c ON c.id_escola = es.id_escolaLEFTJOIN aluno a ON a.id_curso = c.id_cursoLEFTJOIN professor p ON p.id_escola = es.id_escolaGROUPBYROLLUP(ce.nome_centro)ORDERBY centro NULLSLAST;
4 records
centro
total_alunos
media_salario_prof
Centro de Educação, Magistério e Artes
6
4800.00
Centro de Engenharias, Tecnologia e Inovação
6
4366.67
Ciências Humanas, Cidadania e Meio Ambiente
6
6550.00
TOTAL GERAL
18
5114.29
-- Percentil de cada professor no ranking salarial geralSELECT nome, salario,ROUND((PERCENT_RANK() OVER (ORDERBY salario) *100)::NUMERIC, 1) AS percentil,ROUND((CUME_DIST() OVER (ORDERBY salario) *100)::NUMERIC, 1) AS dist_acumuladaFROM professorORDERBY salario;