Consultas com Agrupamento
Consultas com Agrupamento
🌍 Analogia do Mundo Real
Imagine que você é um professor com uma pilha de boletins. Você quer saber:
- A nota média de cada aluno → Agrupar por aluno, calcular a média
- A nota mais alta de cada disciplina → Agrupar por disciplina, encontrar o máximo
- A taxa de aprovação de cada turma → Agrupar por turma, calcular a proporção
É isso que o GROUP BY faz: primeiro divide os dados em grupos, depois realiza estatísticas em cada grupo. As funções de agregação aprendidas na lição anterior calculam um único valor para toda a tabela, enquanto o GROUP BY calcula valores por categoria.
🎯 Conceitos Fundamentais
Sintaxe Básica do GROUP BY
SELECT nome_coluna, funcao_agregacao(nome_coluna)
FROM nome_tabela
WHERE condicao
GROUP BY nome_coluna;
Lógica de execução: Primeiro agrupa pela coluna do GROUP BY, depois aplica a função de agregação a cada grupo.
-- Quantos funcionários existem em cada departamento?
SELECT department_id, COUNT(*) AS total_funcionarios
FROM employees
GROUP BY department_id;
Saída:
department_id total_funcionarios
------------- ------------------
1 3
2 2
3 2
NULL 1
SELECT devem aparecer no GROUP BY ou estar envolvidas em uma função de agregação. Caso contrário, a semântica é ambígua e o banco de dados reportará um erro.
Agregação Pós-Grupamento
O padrão central do GROUP BY é: Agrupar → Agregar.
-- Salário médio por departamento
SELECT department_id, AVG(salary) AS salario_medio
FROM employees
GROUP BY department_id;
Combinações comuns de agregação:
SELECT department_id,
COUNT(*) AS total_funcionarios,
SUM(salary) AS salario_total,
AVG(salary) AS salario_medio,
MAX(salary) AS maior_salario,
MIN(salary) AS menor_salario
FROM employees
GROUP BY department_id;
Agrupamento Multi-Coluna
Você pode agrupar por múltiplas colunas — linhas são consideradas no mesmo grupo apenas quando os valores de todas as colunas especificadas coincidem:
-- Contagem de funcionários por departamento e status
SELECT department_id, status, COUNT(*) AS total_funcionarios
FROM employees
GROUP BY department_id, status;
Saída:
department_id status total_funcionarios
------------- -------- ------------------
1 active 3
2 active 1
2 inactive 1
3 active 2
GROUP BY + ORDER BY
Você pode ordenar após o agrupamento:
-- Salário médio por departamento, do maior para o menor
SELECT department_id, AVG(salary) AS salario_medio
FROM employees
GROUP BY department_id
ORDER BY salario_medio DESC;
WHERE vs HAVING — Filtrando Linhas vs Filtrando Grupos
Este é um dos conceitos mais importantes desta lição:
| Característica | WHERE | HAVING |
|---|---|---|
| Filtra | Linhas (antes do agrupamento) | Grupos (após o agrupamento) |
| Momento de execução | Antes do GROUP BY |
Após o GROUP BY |
| Pode usar funções de agregação | ❌ Não | ✅ Sim |
| Uso | Pode ser usado sozinho | Deve ser usado com GROUP BY |
-- WHERE: Filtra linhas primeiro, depois agrupa
-- Encontrar departamentos com mais de 3 funcionários
SELECT department_id, COUNT(*) AS total_funcionarios
FROM employees
WHERE salary > 5000 -- Primeiro exclui linhas com salário abaixo de 5000
GROUP BY department_id;
-- HAVING: Agrupa primeiro, depois filtra grupos
-- Encontrar departamentos com 3 ou mais funcionários
SELECT department_id, COUNT(*) AS total_funcionarios
FROM employees
GROUP BY department_id
HAVING COUNT(*) >= 3; -- Filtra grupos com menos de 3 membros
-- Combinando WHERE + HAVING
-- Encontrar departamentos com funcionários contratados em 2024 e salário médio > 10000
SELECT department_id, AVG(salary) AS salario_medio
FROM employees
WHERE hire_date >= '2024-01-01' -- Filtra linhas primeiro
GROUP BY department_id
HAVING AVG(salary) > 10000; -- Depois filtra grupos
Ordem de Execução
A ordem de escrita e a ordem de execução das consultas SQL diferem:
Ordem de escrita: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY
Ordem de execução: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
Compreender a ordem de execução é crucial para escrever SQL correto:
FROM— Determina a fonte de dadosWHERE— Filtra linha por linhaGROUP BY— AgrupaHAVING— Filtra gruposSELECT— Seleciona colunas, calcula expressõesORDER BY— Ordena
WHERE — porque quando WHERE é executado, GROUP BY ainda não foi executado, então os resultados da agregação não existem.
📝 Sintaxe Básica
-- Agrupamento básico
SELECT nome_coluna, funcao_agregacao(nome_coluna)
FROM nome_tabela
GROUP BY nome_coluna;
-- Agrupamento multi-coluna
SELECT coluna1, coluna2, funcao_agregacao(nome_coluna)
FROM nome_tabela
GROUP BY coluna1, coluna2;
-- WHERE + GROUP BY + HAVING
SELECT nome_coluna, funcao_agregacao(nome_coluna)
FROM nome_tabela
WHERE condicao_nivel_linha
GROUP BY nome_coluna
HAVING condicao_nivel_agregacao
ORDER BY coluna_ordenacao;
- Colunas no
GROUP BYnão precisam corresponder à ordem das colunas noSELECT, mas devem incluir todas as colunas não agregadas HAVINGpode usar aliases de funções de agregação (suportado em MySQL e PostgreSQL), mas o SQL padrão exige escrever a expressão completa- Valores
NULLsão agrupados juntos noGROUP BY
📌 Exemplos
Exemplo: Estatísticas de Funcionários por Departamento
SELECT d.department_name,
COUNT(e.employee_id) AS total_funcionarios,
AVG(e.salary) AS salario_medio,
SUM(e.salary) AS salario_total
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name
ORDER BY salario_total DESC;
Saída:
department_name total_funcionarios salario_medio salario_total
--------------- ------------------ ------------- -------------
Technology 3 17666.67 53000.00
Finance 2 13500.00 27000.00
Marketing 2 11500.00 23000.00
Administration 1 9000.00 9000.00
LEFT JOIN garante que departamentos sem funcionários também sejam exibidos (com contagem 0). GROUP BY department_name agrupa por nome do departamento.
Exemplo: WHERE + GROUP BY + HAVING Combinados
-- Entre funcionários contratados em 2024, encontrar departamentos com salário médio > 12000
SELECT department_id,
COUNT(*) AS total_funcionarios,
AVG(salary) AS salario_medio
FROM employees
WHERE hire_date >= '2024-01-01'
GROUP BY department_id
HAVING AVG(salary) > 12000
ORDER BY salario_medio DESC;
Saída:
department_id total_funcionarios salario_medio
------------- ------------------ -------------
1 2 18500.00
3 1 14000.00
WHERE hire_date >= '2024-01-01'— Primeiro filtra funcionários contratados em 2024GROUP BY department_id— Agrupa por departamentoHAVING AVG(salary) > 12000— Mantém apenas grupos com salário médio > 12000ORDER BY salario_medio DESC— Ordena por salário médio em ordem decrescente
Exemplo: Agrupamento Multi-Coluna + Condições Complexas
-- Estatísticas de funcionários por departamento e status, mostrando apenas grupos com 2+ membros
SELECT d.department_name,
e.status,
COUNT(*) AS total_funcionarios,
MIN(e.salary) AS menor_salario,
MAX(e.salary) AS maior_salario
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 0
GROUP BY d.department_name, e.status
HAVING COUNT(*) >= 2
ORDER BY d.department_name, total_funcionarios DESC;
Saída:
department_name status total_funcionarios menor_salario maior_salario
--------------- ------ ------------------ ------------- -------------
Finance active 2 12000.00 14000.00
Marketing active 2 10000.00 12000.00
Technology active 3 15000.00 20000.00
WHERE para excluir linhas com salário zero, depois agrupa por departamento + status, HAVING filtra grupos com menos de 2 membros, e finalmente ordena.
🎬 Cenários de Prática
Cenário 1: Relatório de Vendas — Estatísticas Mensais de Pedidos
Gere um relatório de vendas mensal, mostrando apenas meses com 3 ou mais pedidos.
SELECT
EXTRACT(YEAR FROM order_date) AS ano,
EXTRACT(MONTH FROM order_date) AS mes,
COUNT(*) AS qtd_pedidos,
SUM(total_amount) AS valor_total,
AVG(total_amount) AS valor_medio_pedido
FROM orders
WHERE status IN ('completed', 'shipped')
GROUP BY EXTRACT(YEAR FROM order_date), EXTRACT(MONTH FROM order_date)
HAVING COUNT(*) >= 3
ORDER BY ano, mes;
WHERE filtra pedidos concluídos e enviados, agrupa por ano e mês para estatísticas, HAVING filtra meses com menos de 3 pedidos.
Cenário 2: Análise de RH — Encontrando Departamentos de Alto Salário
Encontre departamentos com salário médio superior à média geral da empresa.
SELECT d.department_name,
COUNT(e.employee_id) AS total_funcionarios,
AVG(e.salary) AS salario_medio_dept
FROM departments d
JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_name
HAVING AVG(e.salary) > (SELECT AVG(salary) FROM employees)
ORDER BY salario_medio_dept DESC;
HAVING. Primeiro calcula o salário médio de cada departamento, depois compara com a média geral da empresa. Esta é uma aplicação combinada de funções de agregação e subconsultas.
❓ Perguntas Frequentes
P: Colunas no SELECT devem aparecer no GROUP BY? R: Sim, todas as colunas não agregadas devem aparecer no
GROUP BY. Este é um requisito do padrão SQL. Por exemplo,SELECT a, b, COUNT(*) FROM t GROUP BY acausará um erro porquebnão está noGROUP BYnem envolvido em uma função de agregação.
P: HAVING pode usar aliases do SELECT? R: MySQL e PostgreSQL suportam usar aliases em
HAVING, mas SQL Server não. Para compatibilidade, recomenda-se escrever a expressão completa:HAVING AVG(salary) > 10000em vez deHAVING salario_medio > 10000.
P: Como valores NULL são tratados no GROUP BY? R: Valores
NULLsão agrupados juntos. Sedepartment_idtem valores NULL, todas as linhas com NULL serão colocadas no mesmo grupo. Isso geralmente é útil, mas tenha cuidado para distinguir entre "sem departamento" e "ID do departamento é 0".
P: WHERE e HAVING podem ser usados juntos? R: Sim, e é muito comum.
WHEREfiltra linhas antes do agrupamento,HAVINGfiltra grupos após o agrupamento. Ordem de execução:WHERE→GROUP BY→HAVING.
📖 Resumo
GROUP BYdivide os dados em grupos e aplica funções de agregação a cada grupo- Colunas não agregadas no
SELECTdevem aparecer noGROUP BY - Agrupamento multi-coluna: agrupa por combinações de valores de múltiplas colunas
WHERE: filtra linhas antes do agrupamento, não pode usar funções de agregaçãoHAVING: filtra grupos após o agrupamento, pode usar funções de agregação- Ordem de execução:
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY - Valores
NULLsão agrupados juntos
📝 Exercícios
Exercício 1 (⭐): Consulte o total de funcionários e o salário médio de cada departamento, ordenado por salário médio do maior para o menor.
Exercício 2 (⭐⭐): Consulte o número de funcionários contratados após 2024 em cada departamento, mostrando apenas departamentos com 2 ou mais funcionários.
Exercício 3 (⭐⭐⭐): Consulte o funcionário com o maior salário de cada departamento. Exiba o nome do departamento, nome do funcionário e salário. Dica: isso pode ser alcançado usando uma subconsulta ou a função de janela ROW_NUMBER() (abordada mais tarde).
Próxima Lição
👉 15-advanced-functions - Funções Avançadas: Aprenda funções SQL avançadas, domine processamento de strings, cálculos numéricos, operações com datas e outras habilidades práticas!



