Consultas com Agrupamento

Consultas com Agrupamento

🌍 Analogia do Mundo Real

Imagine que você é um professor com uma pilha de boletins. Você quer saber:

É 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

SQL
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.

SQL
-- Quantos funcionários existem em cada departamento?
SELECT department_id, COUNT(*) AS total_funcionarios
FROM employees
GROUP BY department_id;

Saída:

TEXT
department_id  total_funcionarios
-------------  ------------------
1              3
2              2
3              2
NULL           1
💡 Regra: Colunas no 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.

SQL
-- 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:

SQL
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:

SQL
-- 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:

TEXT
department_id  status    total_funcionarios
-------------  --------  ------------------
1              active    3
2              active    1
2              inactive  1
3              active    2
💡 Entendimento: Agrupamento multi-coluna é semelhante a subtotais multinível no Excel. Primeiro agrupa por departamento, depois por status — cada combinação departamento + status forma um grupo.

GROUP BY + ORDER BY

Você pode ordenar após o agrupamento:

SQL
-- 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
SQL
-- 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
SQL
-- 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:

TEXT
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:

  1. FROM — Determina a fonte de dados
  2. WHERE — Filtra linha por linha
  3. GROUP BY — Agrupa
  4. HAVING — Filtra grupos
  5. SELECT — Seleciona colunas, calcula expressões
  6. ORDER BY — Ordena
💡 Chave: É por isso que funções de agregação não podem ser usadas em 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

SQL
-- 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;
💡 Dica:

  • Colunas no GROUP BY não precisam corresponder à ordem das colunas no SELECT, mas devem incluir todas as colunas não agregadas
  • HAVING pode 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 NULL são agrupados juntos no GROUP BY

📌 Exemplos

Exemplo: Estatísticas de Funcionários por Departamento

SQL
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;
▶ Experimente

Saída:

TEXT
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
💡 Interpretação: Usar 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

SQL
-- 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;
▶ Experimente

Saída:

TEXT
department_id  total_funcionarios  salario_medio
-------------  ------------------  -------------
1              2                   18500.00
3              1                   14000.00
💡 Processo de execução:

  1. WHERE hire_date >= '2024-01-01' — Primeiro filtra funcionários contratados em 2024
  2. GROUP BY department_id — Agrupa por departamento
  3. HAVING AVG(salary) > 12000 — Mantém apenas grupos com salário médio > 12000
  4. ORDER BY salario_medio DESC — Ordena por salário médio em ordem decrescente

Exemplo: Agrupamento Multi-Coluna + Condições Complexas

SQL
-- 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;
▶ Experimente

Saída:

TEXT
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
💡 Interpretação: Primeiro usa 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.

SQL
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;
💡 Abordagem: 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.

SQL
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;
💡 Abordagem: Subconsultas podem ser usadas em 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 a causará um erro porque b não está no GROUP BY nem 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) > 10000 em vez de HAVING salario_medio > 10000.

P: Como valores NULL são tratados no GROUP BY? R: Valores NULL são agrupados juntos. Se department_id tem 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. WHERE filtra linhas antes do agrupamento, HAVING filtra grupos após o agrupamento. Ordem de execução: WHEREGROUP BYHAVING.


📖 Resumo


📝 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!

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%