GROUP BY: agrupando resultados

1. Introdução ao GROUP BY

O GROUP BY é uma das cláusulas mais poderosas do SQL, permitindo transformar linhas individuais em grupos resumidos. Enquanto uma consulta comum retorna uma linha para cada registro da tabela, o GROUP BY agrega esses registros em grupos, aplicando funções de agregação para gerar informações consolidadas.

A sintaxe básica segue este padrão:

SELECT coluna_agrupamento, função_agregacao(coluna)
FROM nome_tabela
GROUP BY coluna_agrupamento;

Imagine uma tabela de vendas com milhares de pedidos. Sem GROUP BY, você veria cada pedido individualmente. Com GROUP BY, pode ver o total vendido por cliente, a média de valor por mês, ou a contagem de pedidos por vendedor.

2. Funcionamento Interno do GROUP BY

Internamente, o SQL processa o GROUP BY em duas etapas principais:

  1. Ordenação: O banco de dados ordena os registros pelos campos especificados no GROUP BY
  2. Agregação: Para cada grupo identificado, aplica as funções de agregação definidas no SELECT

As funções de agregação mais comuns são:
- COUNT() — conta registros
- SUM() — soma valores
- AVG() — calcula média
- MIN() — menor valor
- MAX() — maior valor

Regra fundamental: toda coluna presente no SELECT deve estar no GROUP BY ou ser passada como argumento de uma função de agregação. Caso contrário, o SQL retornará erro.

-- ERRADO: categoria não está no GROUP BY nem é agregada
SELECT cliente_id, categoria, SUM(valor)
FROM vendas
GROUP BY cliente_id;

-- CORRETO
SELECT cliente_id, SUM(valor)
FROM vendas
GROUP BY cliente_id;

3. Agrupamento com uma Única Coluna

Vamos criar um exemplo prático com uma tabela de vendas:

CREATE TABLE vendas (
    id INT PRIMARY KEY,
    cliente VARCHAR(100),
    valor DECIMAL(10,2),
    data_venda DATE
);

INSERT INTO vendas VALUES
(1, 'Ana', 1500.00, '2024-01-15'),
(2, 'Carlos', 800.00, '2024-01-20'),
(3, 'Ana', 2200.00, '2024-02-10'),
(4, 'Beatriz', 950.00, '2024-02-15'),
(5, 'Carlos', 3000.00, '2024-03-05');

Contando pedidos por cliente:

SELECT cliente, COUNT(*) AS total_pedidos
FROM vendas
GROUP BY cliente;

Resultado:

cliente  | total_pedidos
Ana      | 2
Beatriz  | 1
Carlos   | 2

Totalizando valores por cliente:

SELECT cliente, SUM(valor) AS total_gasto
FROM vendas
GROUP BY cliente;

Resultado:

cliente  | total_gasto
Ana      | 3700.00
Beatriz  | 950.00
Carlos   | 3800.00

4. Agrupamento com Múltiplas Colunas

Agrupar por múltiplas colunas cria combinações únicas entre elas. A sintaxe é:

GROUP BY coluna1, coluna2

Exemplo prático: total de vendas por cliente e mês:

SELECT 
    cliente,
    EXTRACT(MONTH FROM data_venda) AS mes,
    SUM(valor) AS total_mes
FROM vendas
GROUP BY cliente, EXTRACT(MONTH FROM data_venda)
ORDER BY cliente, mes;

Resultado:

cliente  | mes | total_mes
Ana      | 1   | 1500.00
Ana      | 2   | 2200.00
Beatriz  | 2   | 950.00
Carlos   | 1   | 800.00
Carlos   | 3   | 3000.00

A hierarquia de agrupamento considera todas as combinações distintas dos valores das colunas listadas.

5. Ordenação de Grupos com ORDER BY

O ORDER BY pode ser combinado com GROUP BY para organizar os resultados. É possível ordenar tanto por colunas de agrupamento quanto por colunas agregadas:

SELECT cliente, SUM(valor) AS total_gasto
FROM vendas
GROUP BY cliente
ORDER BY total_gasto DESC;

Resultado:

cliente  | total_gasto
Carlos   | 3800.00
Ana      | 3700.00
Beatriz  | 950.00

Ordenando por coluna de agrupamento:

SELECT cliente, COUNT(*) AS pedidos
FROM vendas
GROUP BY cliente
ORDER BY cliente ASC;

6. Diferença entre GROUP BY e HAVING

É essencial entender a separação de responsabilidades:

  • WHERE: filtra linhas antes do agrupamento
  • GROUP BY: cria os grupos
  • HAVING: filtra grupos após a agregação

Exemplo: queremos clientes que gastaram mais de R$ 1000,00 no total:

SELECT cliente, SUM(valor) AS total_gasto
FROM vendas
WHERE valor > 0  -- filtra linhas individuais
GROUP BY cliente
HAVING SUM(valor) > 1000.00  -- filtra grupos
ORDER BY total_gasto DESC;

Resultado:

cliente  | total_gasto
Carlos   | 3800.00
Ana      | 3700.00

Note que Beatriz (R$ 950,00) foi excluída pelo HAVING.

7. Cuidados e Boas Práticas

Erro comum: colocar colunas não agregadas fora do GROUP BY.

-- ERRADO
SELECT cliente, data_venda, SUM(valor)
FROM vendas
GROUP BY cliente;

-- CORRETO (se quiser agrupar por cliente e data)
SELECT cliente, data_venda, SUM(valor)
FROM vendas
GROUP BY cliente, data_venda;

Desempenho: índices nas colunas usadas no GROUP BY podem acelerar significativamente a consulta, especialmente em tabelas grandes.

Clareza com alias: sempre nomeie suas colunas agregadas com alias descritivos:

SELECT 
    cliente,
    COUNT(*) AS total_pedidos,
    SUM(valor) AS receita_total,
    AVG(valor) AS ticket_medio
FROM vendas
GROUP BY cliente;

8. Exemplo Completo Passo a Passo

Vamos criar um cenário realista de pedidos:

-- Criação da tabela
CREATE TABLE pedidos (
    id INT PRIMARY KEY,
    cliente VARCHAR(100),
    produto VARCHAR(100),
    quantidade INT,
    preco_unitario DECIMAL(10,2),
    data_pedido DATE,
    status VARCHAR(20)
);

-- Inserção de dados
INSERT INTO pedidos VALUES
(1, 'Ana', 'Notebook', 1, 3500.00, '2024-01-10', 'Entregue'),
(2, 'Carlos', 'Mouse', 3, 150.00, '2024-01-12', 'Entregue'),
(3, 'Ana', 'Teclado', 2, 200.00, '2024-01-15', 'Entregue'),
(4, 'Beatriz', 'Monitor', 1, 1200.00, '2024-02-01', 'Pendente'),
(5, 'Carlos', 'Notebook', 1, 3500.00, '2024-02-05', 'Entregue'),
(6, 'Ana', 'Mouse', 5, 150.00, '2024-02-10', 'Pendente'),
(7, 'Beatriz', 'Teclado', 1, 200.00, '2024-02-15', 'Entregue'),
(8, 'Carlos', 'Monitor', 2, 1200.00, '2024-03-01', 'Pendente');

Consulta completa com COUNT, SUM, AVG, HAVING e ORDER BY:

SELECT 
    cliente,
    COUNT(*) AS total_pedidos,
    SUM(quantidade * preco_unitario) AS valor_total,
    AVG(quantidade * preco_unitario) AS ticket_medio
FROM pedidos
WHERE status = 'Entregue'
GROUP BY cliente
HAVING COUNT(*) >= 2
ORDER BY valor_total DESC;

Resultado:

cliente | total_pedidos | valor_total | ticket_medio
Ana     | 3             | 4200.00     | 1400.00
Carlos  | 2             | 3800.00     | 1900.00

O que aconteceu:
1. WHERE status = 'Entregue' filtrou apenas pedidos entregues (excluiu pedidos de Beatriz e um de Carlos)
2. GROUP BY cliente agrupou por cliente
3. COUNT, SUM e AVG calcularam as métricas por grupo
4. HAVING COUNT(*) >= 2 manteve apenas clientes com 2 ou mais pedidos (excluiu Beatriz, que tinha 1)
5. ORDER BY valor_total DESC ordenou do maior para o menor valor

Este exemplo demonstra como GROUP BY transforma dados brutos em informações gerenciais valiosas, essenciais para análises de negócio, relatórios financeiros e dashboards.

Referências