Funções de Agregação

Autor

Douglas Braga

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 professores
SELECT ROUND(AVG(salario), 2) AS media_salario
FROM   professor;
1 records
media_salario
5114.29
-- Quantos professores há no banco?
SELECT COUNT(*) AS total_professores
FROM   professor;
1 records
total_professores
7
-- Quantas escolas distintas têm professores?
SELECT COUNT(DISTINCT id_escola) AS escolas_com_professor
FROM   professor;
1 records
escolas_com_professor
3
-- Maior e menor salário entre os professores
SELECT MAX(salario) AS maior_salario,
       MIN(salario) AS menor_salario
FROM   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 escola
SELECT id_escola,
       ROUND(AVG(salario), 2) AS media_salario,
       SUM(salario)           AS total_salarios,
       COUNT(*)               AS n_professores
FROM   professor
GROUP BY id_escola
ORDER BY 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 curso
SELECT id_curso, COUNT(*) AS n_disciplinas
FROM   disciplina
GROUP BY id_curso
ORDER BY 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 BY
SELECT id_escola, nome, AVG(salario)
FROM   professor
GROUP BY 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:

  1. FROM — determina as tabelas
  2. WHERE — filtra linhas individuais (antes da agregação)
  3. GROUP BY — agrupa as linhas restantes
  4. Funções de agregação — calculadas por grupo
  5. HAVING — filtra grupos (depois da agregação)
  6. SELECT — projeta as colunas
  7. ORDER BY — ordena o resultado
-- Escolas com salário médio acima de 4000
SELECT id_escola, ROUND(AVG(salario), 2) AS media_salario
FROM   professor
GROUP BY id_escola
HAVING AVG(salario) > 4000
ORDER BY 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çado
SELECT id_curso, COUNT(*) AS disciplinas_avancadas
FROM   disciplina
WHERE  semestre >= 4
GROUP BY id_curso
HAVING COUNT(*) >= 2
ORDER BY 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 > 6000
SELECT id_escola, ROUND(AVG(salario), 2) AS media_salario
FROM   professor
WHERE  ch_semanal = 40
GROUP BY id_escola
HAVING AVG(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 regime
SELECT id_escola, ch_semanal,
       COUNT(*)            AS n_professores,
       SUM(salario)        AS total_salarios,
       ROUND(AVG(salario), 2) AS media_salario
FROM   professor
GROUP BY id_escola, ch_semanal
ORDER BY 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 cadastradas
SELECT id_curso, COUNT(*) AS n_disciplinas,
       MIN(carga_horaria) AS menor_carga,
       MAX(carga_horaria) AS maior_carga
FROM   disciplina
GROUP BY id_curso
HAVING COUNT(*) > 6
ORDER BY 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_geral
FROM   matricula_disciplina
GROUP BY id_aluno
HAVING COUNT(*) FILTER (WHERE aprovado = 0) > 0
ORDER BY 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.