As funções de agregação calculam um único valor a partir de um conjunto de linhas. São amplamente usadas para obter estatísticas sobre os dados.
As Cinco Funções Básicas
Função
Descrição
Aplica-se a
AVG(coluna)
Média aritmética dos valores
Numérico
MIN(coluna)
Menor valor
Numérico, string, data
MAX(coluna)
Maior valor
Numérico, string, data
SUM(coluna)
Soma de todos os valores
Numérico
COUNT(*)
Número de linhas
Qualquer
COUNT(coluna)
Número de linhas com valor não nulo
Qualquer
Todas as funções, exceto COUNT(*), ignoram valores nulos.
Agregação Sem GROUP BY
Quando nenhuma cláusula GROUP BY é especificada, a função agrega todas as linhas da tabela (ou do subconjunto filtrado pelo WHERE).
-- Salário médio de todos os professoresSELECTROUND(AVG(salario), 2) AS media_salarioFROM professor;
1 records
media_salario
5114.29
-- Quantos professores há no banco?SELECTCOUNT(*) AS total_professoresFROM professor;
1 records
total_professores
7
-- Quantas escolas distintas têm professores?SELECTCOUNT(DISTINCT id_escola) AS escolas_com_professorFROM professor;
1 records
escolas_com_professor
3
-- Maior e menor salário entre os professoresSELECTMAX(salario) AS maior_salario,MIN(salario) AS menor_salarioFROM professor;
1 records
maior_salario
menor_salario
6700
3100
GROUP BY: Agrupando por Categoria
A cláusula GROUP BY divide as linhas em grupos e aplica a função de agregação a cada grupo separadamente.
-- Salário médio, total e número de professores por escolaSELECT id_escola,ROUND(AVG(salario), 2) AS media_salario,SUM(salario) AS total_salarios,COUNT(*) AS n_professoresFROM professorGROUPBY id_escolaORDERBY media_salario DESC;
3 records
id_escola
media_salario
total_salarios
n_professores
11
6550.00
13100
2
21
4800.00
9600
2
31
4366.67
13100
3
-- Número de disciplinas por cursoSELECT id_curso, COUNT(*) AS n_disciplinasFROM disciplinaGROUPBY id_cursoORDERBY n_disciplinas DESC;
3 records
id_curso
n_disciplinas
311
8
111
8
211
8
Erro Comum: Atributo Fora do GROUP BY
Aviso
Todo atributo listado no SELECT que não é uma função de agregação deve aparecer no GROUP BY. O exemplo abaixo causaria erro:
-- INCORRETO: 'nome' não está no GROUP BYSELECT id_escola, nome, AVG(salario)FROM professorGROUPBY id_escola;-- ERROR: column "professor.nome" must appear in the GROUP BY clause-- or be used in an aggregate function
A razão: para cada grupo (um id_escola), há vários nomes de professores. O banco não sabe qual nome retornar.
HAVING: Filtrando Grupos
A cláusula HAVING filtra os grupos resultantes do GROUP BY, da mesma forma que o WHERE filtra linhas individuais.
Ordem de execução de uma consulta com agregação:
FROM — determina as tabelas
WHERE — filtra linhas individuais (antes da agregação)
GROUP BY — agrupa as linhas restantes
Funções de agregação — calculadas por grupo
HAVING — filtra grupos (depois da agregação)
SELECT — projeta as colunas
ORDER BY — ordena o resultado
-- Escolas com salário médio acima de 4000SELECT id_escola, ROUND(AVG(salario), 2) AS media_salarioFROM professorGROUPBY id_escolaHAVINGAVG(salario) >4000ORDERBY media_salario DESC;
3 records
id_escola
media_salario
11
6550.00
21
4800.00
31
4366.67
Todas as três escolas satisfazem HAVING AVG(salario) > 4000: id_escola 11 (6.550), 21 (4.800) e 31 (4.367).
-- Cursos com pelo menos 2 disciplinas de semestre 4 ou mais avançadoSELECT id_curso, COUNT(*) AS disciplinas_avancadasFROM disciplinaWHERE semestre >=4GROUPBY id_cursoHAVINGCOUNT(*) >=2ORDERBY disciplinas_avancadas DESC;
3 records
id_curso
disciplinas_avancadas
111
6
311
6
211
6
WHERE vs. HAVING
Aspecto
WHERE
HAVING
Opera sobre
Linhas individuais
Grupos
Executado
Antes do GROUP BY
Depois do GROUP BY
Pode usar agregação?
Não
Sim
-- WHERE filtra antes: considera apenas professores de 40h,-- depois agrupa por escola e filtra com média > 6000SELECT id_escola, ROUND(AVG(salario), 2) AS media_salarioFROM professorWHERE ch_semanal =40GROUPBY id_escolaHAVINGAVG(salario) >6000;
3 records
id_escola
media_salario
11
6550
21
6400
31
6700
Para Praticar
-- GROUP BY com múltiplas colunas: distribuição por escola e regimeSELECT id_escola, ch_semanal,COUNT(*) AS n_professores,SUM(salario) AS total_salarios,ROUND(AVG(salario), 2) AS media_salarioFROM professorGROUPBY id_escola, ch_semanalORDERBY id_escola, ch_semanal;
5 records
id_escola
ch_semanal
n_professores
total_salarios
media_salario
11
40
2
13100
6550
21
20
1
3200
3200
21
40
1
6400
6400
31
20
2
6400
3200
31
40
1
6700
6700
-- HAVING com COUNT: cursos com mais de 6 disciplinas cadastradasSELECT id_curso, COUNT(*) AS n_disciplinas,MIN(carga_horaria) AS menor_carga,MAX(carga_horaria) AS maior_cargaFROM disciplinaGROUPBY id_cursoHAVINGCOUNT(*) >6ORDERBY n_disciplinas DESC;
3 records
id_curso
n_disciplinas
menor_carga
maior_carga
311
8
100
180
111
8
60
100
211
8
40
80
-- Alunos com pelo menos uma reprovação: comparar COUNT(*) com COUNT(aprovado=0)-- Usando filtro dentro do COUNT com FILTER (SQL padrão)SELECT id_aluno,COUNT(*) AS total_disciplinas,COUNT(*) FILTER (WHERE aprovado =0) AS reprovacoes,COUNT(*) FILTER (WHERE aprovado =1) AS aprovacoes,ROUND(AVG(nota), 2) AS media_geralFROM matricula_disciplinaGROUPBY id_alunoHAVINGCOUNT(*) FILTER (WHERE aprovado =0) >0ORDERBY reprovacoes DESC;
6 records
id_aluno
total_disciplinas
reprovacoes
aprovacoes
media_geral
2024311002
4
2
2
4.90
2023311001
6
1
5
7.22
2023211002
6
1
5
6.10
2025311002
2
1
1
4.00
2023311002
6
1
5
6.33
2024111002
4
1
3
5.90
Nota
COUNT(*) FILTER (WHERE condição) é uma extensão SQL:2003 suportada pelo PostgreSQL. Ela conta apenas as linhas que satisfazem a condição dentro do grupo — equivale a COUNT(CASE WHEN cond THEN 1 END), mas mais legível.