Início / Trilha 1 / Conteúdo / Tópico 13
13

GROUP BY e Agregação

Funções de agregação, agrupamento e filtragem de grupos

📖

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;
Voltar ao Índice Próximo: Views e Synonyms