Definição
Funções de Agregação
As funções de agregação realizam cálculos em um conjunto de valores e retornam um único valor. São fundamentais para análise de dados e geração de relatórios estatísticos.
COUNT
Conta o número de linhas ou valores não-nulos em uma coluna.
SUM
Calcula a soma de valores numéricos em uma coluna.
AVG
Calcula a média aritmética dos valores numéricos.
MIN / MAX
Retorna o menor ou maior valor em uma coluna.
COUNT: Diferenças Importantes
COUNT(*)
Conta todas as linhas da tabela, incluindo linhas com valores NULL.
SELECT COUNT(*) FROM funcionarios;
-- Retorna: 150 (total de funcionários)
COUNT(coluna)
Conta apenas valores não-nulos na coluna especificada.
SELECT COUNT(comissao) FROM funcionarios;
-- Retorna: 35 (apenas funcionários com comissão)
COUNT(DISTINCT coluna)
Conta apenas valores únicos e não-nulos na coluna.
SELECT COUNT(DISTINCT departamento_id) FROM funcionarios;
-- Retorna: 11 (quantidade de departamentos diferentes)
GROUP BY - Agrupando Resultados
A cláusula GROUP BY agrupa linhas que possuem valores iguais em colunas específicas, permitindo aplicar funções de agregação a cada grupo.
Sintaxe Básica:
SELECT coluna_agrupamento, FUNCAO_AGREGACAO(coluna)
FROM tabela
WHERE condicao -- Filtra ANTES de agrupar
GROUP BY coluna_agrupamento
HAVING condicao_grupo -- Filtra DEPOIS de agrupar
ORDER BY coluna;
Aplicação Prática
Funções de Agregação Básicas
-- Estatísticas gerais de salários
SELECT
COUNT(*) AS total_funcionarios,
COUNT(comissao) AS funcionarios_com_comissao,
COUNT(DISTINCT departamento_id) AS total_departamentos,
SUM(salario) AS folha_pagamento_total,
AVG(salario) AS salario_medio,
MIN(salario) AS menor_salario,
MAX(salario) AS maior_salario,
MAX(salario) - MIN(salario) AS diferenca_salarial
FROM funcionarios;
-- Resultado:
-- TOTAL_FUNCIONARIOS FUNCIONARIOS_COM_COMISSAO TOTAL_DEPARTAMENTOS FOLHA_PAGAMENTO_TOTAL SALARIO_MEDIO MENOR_SALARIO MAIOR_SALARIO DIFERENCA_SALARIAL
-- 107 35 11 691416.00 6461.83 2100.00 24000.00 21900.00
GROUP BY - Relatórios por Grupo
-- Estatísticas salariais por departamento
SELECT
departamento_id,
COUNT(*) AS qtd_funcionarios,
SUM(salario) AS folha_total,
AVG(salario) AS salario_medio,
MIN(salario) AS menor_salario,
MAX(salario) AS maior_salario
FROM funcionarios
GROUP BY departamento_id
ORDER BY folha_total DESC;
-- Resultado:
-- DEPARTAMENTO_ID QTD_FUNCIONARIOS FOLHA_TOTAL SALARIO_MEDIO MENOR_SALARIO MAIOR_SALARIO
-- 50 45 156400.00 3475.56 2100.00 8200.00
-- 80 34 304500.00 8955.88 6100.00 14000.00
-- 100 6 51608.00 8601.33 6900.00 12008.00
HAVING - Filtrando Grupos
A cláusula HAVING filtra grupos após a agregação, enquanto WHERE filtra linhas antes da agregação.
-- Departamentos com mais de 5 funcionários e salário médio > 5000
SELECT
departamento_id,
COUNT(*) AS qtd_funcionarios,
AVG(salario) AS salario_medio,
SUM(salario) AS folha_total
FROM funcionarios
WHERE salario > 2000 -- Filtra ANTES de agrupar
GROUP BY departamento_id
HAVING COUNT(*) > 5 -- Filtra DEPOIS de agrupar
AND AVG(salario) > 5000
ORDER BY salario_medio DESC;
-- Diferença entre WHERE e HAVING:
-- WHERE: Filtra linhas individuais antes da agregação
-- HAVING: Filtra grupos resultantes após a agregação
GROUP BY com Múltiplas Colunas
-- Estatísticas por departamento e cargo
SELECT
departamento_id,
cargo_id,
COUNT(*) AS qtd_funcionarios,
AVG(salario) AS salario_medio
FROM funcionarios
GROUP BY departamento_id, cargo_id
ORDER BY departamento_id, cargo_id;
-- Combinando GROUP BY com JOIN
SELECT
d.nome_departamento,
j.titulo_cargo,
COUNT(*) AS qtd_funcionarios,
AVG(f.salario) AS salario_medio,
SUM(f.salario) AS folha_total
FROM funcionarios f
JOIN departamentos d ON f.departamento_id = d.id_departamento
JOIN cargos j ON f.cargo_id = j.id_cargo
GROUP BY d.nome_departamento, j.titulo_cargo
HAVING COUNT(*) >= 2
ORDER BY d.nome_departamento, salario_medio DESC;
Erros Comuns e Como Evitá-los
Erro: Coluna não agregada no SELECT
-- INCORRETO: nome não está no GROUP BY nem em função de agregação
SELECT departamento_id, nome, AVG(salario)
FROM funcionarios
GROUP BY departamento_id;
-- ORA-00979: not a GROUP BY expression
Regra de Ouro do GROUP BY
Toda coluna no SELECT deve estar:
- Na cláusula GROUP BY, OU
- Dentro de uma função de agregação (COUNT, SUM, AVG, MIN, MAX)
-- CORRETO: Usando MAX para incluir o nome
SELECT
departamento_id,
MAX(nome) KEEP (DENSE_RANK FIRST ORDER BY salario DESC) AS funcionario_maior_salario,
AVG(salario) AS salario_medio
FROM funcionarios
GROUP BY departamento_id;
-- CORRETO: Incluindo todas as colunas não agregadas no GROUP BY
SELECT departamento_id, cargo_id, AVG(salario)
FROM funcionarios
GROUP BY departamento_id, cargo_id;
Resultado Esperado
O que você deve dominar após este tópico:
Funções de agregação
Usar COUNT, SUM, AVG, MIN, MAX corretamente
Diferenças do COUNT
Distinguir COUNT(*), COUNT(coluna), COUNT(DISTINCT)
Agrupar dados
Usar GROUP BY com uma ou múltiplas colunas
Filtrar grupos
Distinguir e usar WHERE vs HAVING corretamente
Evitar erros comuns
Identificar e corrigir problemas de agregação
Gerar relatórios
Criar análises estatísticas e relatórios gerenciais
Exercícios Práticos
Exercício 1: Análise de Vendas
Crie uma consulta que mostre o total de vendas, ticket médio e quantidade de pedidos por região, apenas para regiões com mais de 100 pedidos, ordenado por total de vendas decrescente.
Ver Solução
SELECT
r.nome_regiao,
COUNT(*) AS qtd_pedidos,
SUM(p.valor_total) AS total_vendas,
AVG(p.valor_total) AS ticket_medio,
MIN(p.valor_total) AS menor_venda,
MAX(p.valor_total) AS maior_venda
FROM pedidos p
JOIN clientes c ON p.cliente_id = c.id_cliente
JOIN regioes r ON c.regiao_id = r.id_regiao
GROUP BY r.nome_regiao
HAVING COUNT(*) > 100
ORDER BY total_vendas DESC;
Exercício 2: Relatório de RH
Identifique departamentos onde o salário médio está acima da média geral da empresa e que tenham pelo menos 3 funcionários.
Ver Solução
SELECT
d.nome_departamento,
COUNT(*) AS qtd_funcionarios,
AVG(f.salario) AS salario_medio_depto,
(SELECT AVG(salario) FROM funcionarios) AS salario_medio_empresa,
AVG(f.salario) - (SELECT AVG(salario) FROM funcionarios) AS diferenca
FROM funcionarios f
JOIN departamentos d ON f.departamento_id = d.id_departamento
GROUP BY d.nome_departamento
HAVING COUNT(*) >= 3
AND AVG(f.salario) > (SELECT AVG(salario) FROM funcionarios)
ORDER BY salario_medio_depto DESC;
Exercício 3: Análise Temporal
Crie um relatório mostrando a quantidade de contratações e a média salarial por ano e mês, apenas para meses com mais de 2 contratações.
Ver Solução
SELECT
EXTRACT(YEAR FROM data_admissao) AS ano,
EXTRACT(MONTH FROM data_admissao) AS mes,
TO_CHAR(data_admissao, 'MONTH', 'NLS_DATE_LANGUAGE=PORTUGUESE') AS nome_mes,
COUNT(*) AS qtd_contratacoes,
AVG(salario) AS salario_medio_contratados,
MIN(salario) AS menor_salario,
MAX(salario) AS maior_salario
FROM funcionarios
GROUP BY
EXTRACT(YEAR FROM data_admissao),
EXTRACT(MONTH FROM data_admissao),
TO_CHAR(data_admissao, 'MONTH', 'NLS_DATE_LANGUAGE=PORTUGUESE')
HAVING COUNT(*) > 2
ORDER BY ano DESC, mes DESC;