Quando uma tabela cresce, olhar registro por registro deixa de responder perguntas como “quantos produtos existem em cada categoria?”, “qual é o valor médio?” ou “quais categorias têm estoque total acima de 100?”. Em SQL, as funções de agregação resumem várias linhas, GROUP BY separa essas linhas em grupos e HAVING filtra os grupos depois do cálculo.
Comece pelas funções de agregação
As mais úteis no início são COUNT, SUM, AVG, MIN e MAX. Sem agrupamento, elas resumem o conjunto inteiro selecionado.
SELECT COUNT(*) AS total_produtos,
AVG(preco) AS preco_medio,
MIN(preco) AS menor_preco,
MAX(preco) AS maior_preco
FROM produtos;
COUNT(*) conta linhas. AVG(preco) calcula a média dos valores não nulos da coluna. MIN e MAX encontram os extremos.
GROUP BY cria um resumo para cada grupo
Agora imagine que a pergunta seja “quantos produtos existem em cada categoria?”. A categoria precisa definir os grupos:
SELECT categoria, COUNT(*) AS quantidade
FROM produtos
GROUP BY categoria
ORDER BY quantidade DESC;
O banco reúne as linhas que têm a mesma categoria e calcula COUNT(*) separadamente para cada conjunto. O resultado tem uma linha por categoria, não uma linha por produto.
Somando estoque por categoria
SELECT categoria,
SUM(estoque) AS estoque_total,
ROUND(AVG(preco), 2) AS preco_medio
FROM produtos
GROUP BY categoria
ORDER BY categoria;
A mesma consulta pode calcular mais de um resumo por grupo. O ROUND apenas facilita a leitura da média.
SELECT sem estar dentro de uma função de agregação, normalmente ela precisa participar do GROUP BY.WHERE filtra linhas; HAVING filtra grupos
Essa diferença é central. WHERE atua antes do agrupamento. HAVING testa o resultado produzido para cada grupo.
SELECT categoria, SUM(estoque) AS estoque_total
FROM produtos
WHERE preco > 20
GROUP BY categoria
HAVING SUM(estoque) > 100
ORDER BY estoque_total DESC;
Primeiro entram apenas produtos com preço acima de 20. Depois eles são agrupados por categoria. Por fim, permanecem somente as categorias cujo estoque somado supera 100.
Um erro que ajuda a entender
-- Evite isto:
SELECT categoria, COUNT(*)
FROM produtos
WHERE COUNT(*) > 3
GROUP BY categoria;
WHERE não pode decidir com base em COUNT(*) porque a contagem ainda não foi produzida nessa etapa. A condição agregada pertence ao HAVING:
SELECT categoria, COUNT(*) AS quantidade
FROM produtos
GROUP BY categoria
HAVING COUNT(*) > 3;
Laboratório: relatório de vendas no PostgreSQL
Crie uma tabela pequena para experimentar sem depender de outro projeto.
CREATE TABLE vendas (
id SERIAL PRIMARY KEY,
categoria VARCHAR(40) NOT NULL,
produto VARCHAR(80) NOT NULL,
quantidade INTEGER NOT NULL,
valor_unitario NUMERIC(10,2) NOT NULL
);
INSERT INTO vendas (categoria, produto, quantidade, valor_unitario) VALUES
('Informática','Mouse',3,90.00),
('Informática','Teclado',2,150.00),
('Informática','Monitor',1,900.00),
('Papelaria','Caderno',10,22.00),
('Papelaria','Caneta',30,4.50),
('Acessórios','Suporte',4,75.00);
1. Faturamento por categoria
SELECT categoria,
SUM(quantidade * valor_unitario) AS faturamento
FROM vendas
GROUP BY categoria
ORDER BY faturamento DESC;
A expressão multiplica quantidade por valor unitário em cada linha; depois SUM soma esses valores dentro de cada categoria.
2. Categorias que venderam pelo menos cinco unidades
SELECT categoria, SUM(quantidade) AS unidades
FROM vendas
GROUP BY categoria
HAVING SUM(quantidade) >= 5
ORDER BY unidades DESC;
3. Média de preço somente para itens com valor acima de R$ 20
SELECT categoria,
ROUND(AVG(valor_unitario), 2) AS media
FROM vendas
WHERE valor_unitario > 20
GROUP BY categoria
ORDER BY media DESC;
Observe que o filtro individual usa WHERE. Se a exigência fosse “mostrar somente categorias cuja média seja maior que 100”, seria HAVING AVG(valor_unitario) > 100.
Desafio prático
Produza um relatório com categoria, quantidade total de unidades e faturamento total. Mostre somente categorias com faturamento acima de R$ 300 e ordene do maior faturamento para o menor. Antes de executar, escreva em uma frase o papel de GROUP BY, HAVING e ORDER BY na sua consulta.
Quatro erros comuns
1. Coluna solta no SELECT. Misturar uma coluna comum com agregação sem agrupá-la costuma gerar erro. 2. Usar WHERE para filtrar uma soma ou média. Condições sobre resultados agregados pertencem ao HAVING. 3. Confundir COUNT(*) com COUNT(coluna). O primeiro conta linhas; o segundo ignora valores nulos naquela coluna. 4. Agrupar demais. Cada coluna adicional no GROUP BY divide os dados em grupos mais específicos e pode destruir o resumo desejado.
Caso completo: relatório mensal que pode ser conferido
Uma consulta deve mostrar categorias com pelo menos três vendas concluídas e faturamento acima de R$ 500. Pedidos cancelados não entram. O filtro de status atua nas linhas antes do agrupamento; as regras sobre quantidade e soma atuam depois.
SELECT categoria,
COUNT(*) AS quantidade_vendas,
SUM(valor_total) AS faturamento
FROM vendas
WHERE status = 'CONCLUIDA'
GROUP BY categoria
HAVING COUNT(*) >= 3
AND SUM(valor_total) > 500
ORDER BY faturamento DESC;
Como conferir sem confiar cegamente na consulta
- Filtre manualmente uma amostra e retire os cancelados.
- Separe as linhas por categoria.
- Conte vendas e some valores de cada grupo.
- Aplique os dois critérios do
HAVING. - Compare colunas, ordem e quantidade de grupos com o SQL.
| Etapa lógica | Papel | Erro comum |
|---|---|---|
WHERE | Escolher linhas válidas | Filtrar agregados que ainda não existem |
GROUP BY | Formar grupos | Selecionar coluna sem agrupar nem agregar |
HAVING | Escolher grupos | Usá-lo no lugar de todo filtro de linha |
ORDER BY | Apresentar o resultado | Supor uma ordem sem declará-la |
Teste de consistência
Crie duas versões do relatório: uma detalhada, com as linhas que participam, e outra agrupada. Use a primeira para justificar cada total da segunda. Inclua uma categoria com duas vendas, outra com três que somam exatamente R$ 500 e outra que atende aos dois critérios.
Perguntas frequentes
Posso usar GROUP BY sem função de agregação?
Sim, mas muitas vezes DISTINCT comunica melhor a intenção quando você só quer combinações únicas. O maior valor do GROUP BY aparece quando ele organiza cálculos por grupo.
HAVING substitui WHERE?
Não. Use WHERE para excluir linhas antes da agregação e HAVING para excluir grupos depois dela.
COUNT(*) conta valores NULL?
Ele conta as linhas. Já COUNT(nome_da_coluna) conta somente as linhas em que essa coluna não é nula.
Conclusão
O salto de consultas simples para relatórios começa quando você deixa de perguntar apenas “quais linhas existem?” e passa a perguntar “o que esses dados mostram em conjunto?”. Funções de agregação fazem os cálculos, GROUP BY define os grupos e HAVING seleciona os grupos relevantes. Com isso, SQL passa a responder perguntas de análise sem precisar exportar os dados para outra ferramenta.
Referências bibliográficas
- BEAULIEU, Alan. Learning SQL . 3. ed. O’Reilly Media, 2020.
- ELMASRI, Ramez; NAVATHE, Shamkant B. Fundamentals of Database Systems . 7. ed. Pearson, 2022.
Documentação técnica: PostgreSQL Global Development Group. Aggregate Functions e tutorial de agregação.