O GROUP BY reúne em grupos as linhas que compartilham um valor, para que funções de agregação como COUNT, SUM e AVG retornem um resultado por grupo em vez de um para a tabela inteira. Os exemplos funcionam em PostgreSQL, MySQL, SQLite e SQL Server.
Como usar o GROUP BY
Informe a coluna de agrupamento depois do GROUP BY e coloque todas as outras colunas selecionadas dentro de uma função de agregação: COUNT, SUM, AVG, MIN ou MAX. Você obtém uma linha para cada valor distinto:
SELECT group_column, AGGREGATE(value_column)
FROM table_name
GROUP BY group_column;
-- Input: scores
-- player | team | points
-- -------+------+-------
-- Ana | Red | 12
-- Ben | Blue | 9
-- Cleo | Red | 7
-- Dev | Blue | 15
-- Eli | Red | 4
SELECT team, SUM(points) AS total_points, COUNT(*) AS players
FROM scores
GROUP BY team;
-- Output:
-- team | total_points | players
-- -----+--------------+--------
-- Red | 23 | 3
-- Blue | 24 | 2
Cinco linhas viram duas, uma por time, e cada agregação só enxerga as linhas daquele time.
Regras importantes
- Toda coluna selecionada precisa estar no GROUP BY ou dentro de uma agregação. Caso contrário, não há um valor único para mostrar no grupo, e a consulta falha:
-- Input: products
-- name | category
-- --------+---------
-- Blender | Kitchen
-- Toaster | Kitchen
-- Pillow | Bedroom
SELECT name, category, COUNT(*) AS product_count
FROM products
GROUP BY category;
-- ERROR: column "products.name" must appear in the GROUP BY clause
-- or be used in an aggregate function
-- (SQLite runs it anyway and takes name from one row in each group)
Corrija adicionando a coluna ao GROUP BY ou agregando-a também, com MIN(name), por exemplo.
- NULLs formam um único grupo. Todas as linhas com NULL na coluna de agrupamento caem no mesmo grupo. Use COALESCE para dar um rótulo a ele:
-- Input: products
-- name | category
-- ------------+-----------
-- Notebook | Stationery
-- Gift Card | NULL
-- Pen Set | Stationery
-- Mystery Box | NULL
-- Stapler | Stationery
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category;
-- Output:
-- category | product_count
-- -----------+--------------
-- NULL | 2
-- Stationery | 3
SELECT COALESCE(category, 'Uncategorized') AS category, COUNT(*) AS product_count
FROM products
GROUP BY category;
-- Output:
-- category | product_count
-- --------------+--------------
-- Uncategorized | 2
-- Stationery | 3
-
O WHERE filtra linhas antes do agrupamento; o HAVING filtra grupos depois. Só o HAVING pode testar uma agregação como
COUNT(*) > 2. Tudo o que não precisa de agregação vai no WHERE, assim menos linhas são agrupadas. - Os grupos não voltam em nenhuma ordem específica. SQLite e SQL Server, por exemplo, listam Blue antes de Red na consulta de pontuações acima. Adicione ORDER BY sempre que a ordem importar.
Sintaxe alternativa
Você pode agrupar por um alias de coluna, ou pela posição da coluna na lista do SELECT, em vez de repetir uma expressão:
-- Input: orders
-- order_id | amount
-- ---------+-------
-- 1 | 35.00
-- 2 | 240.00
-- 3 | 18.50
-- 4 | 120.00
-- 5 | 64.00
SELECT CASE WHEN amount >= 100 THEN 'large' ELSE 'small' END AS order_size,
COUNT(*) AS order_count
FROM orders
GROUP BY order_size -- or GROUP BY 1
ORDER BY order_size;
-- SQL Server allows neither: repeat the CASE expression in GROUP BY
-- Output:
-- order_size | order_count
-- -----------+------------
-- large | 2
-- small | 3
O ROLLUP adiciona subtotais e um total geral. As linhas de subtotal mostram NULL nas colunas que elas somam:
-- Input: sales
-- region | category | amount
-- -------+----------+-------
-- East | Books | 100.00
-- East | Toys | 50.00
-- West | Books | 80.00
-- East | Books | 40.00
SELECT region, category, SUM(amount) AS total_sales
FROM sales
GROUP BY ROLLUP(region, category);
-- MySQL: GROUP BY region, category WITH ROLLUP
-- SQLite has no ROLLUP
-- Output:
-- region | category | total_sales
-- -------+----------+------------
-- East | Books | 140.00
-- East | Toys | 50.00
-- East | NULL | 190.00
-- West | Books | 80.00
-- West | NULL | 80.00
-- NULL | NULL | 270.00
Um GROUP BY sem agregação retorna cada valor distinto uma vez, o mesmo que SELECT DISTINCT category FROM products.
Quando usar o GROUP BY?
Use GROUP BY sempre que uma pergunta disser “por” ou “para cada”: pedidos por cliente, vendas por mês, funcionários por departamento. A maioria dos usos se encaixa em um destes quatro casos.
Contar linhas em cada grupo
Quantos pedidos há em cada status, quantos produtos há em cada categoria, quantas pessoas se cadastraram em cada dia. COUNT(*) conta todas as linhas do grupo, e COUNT(column) conta só as linhas em que essa coluna não é NULL.
Total, média ou intervalo de cada grupo
SUM, AVG, MIN e MAX transformam os valores de cada grupo em um único número: receita por cliente, valor médio do pedido, o produto mais barato e o mais caro de uma categoria. Uma consulta pode retornar quantos deles você precisar.
Detalhar resultados por mais de uma coluna
Liste várias colunas depois do GROUP BY para obter uma linha para cada combinação: vendas por região por mês, estoque por depósito por tipo de produto. Só aparecem as combinações que existem nos dados.
Manter só os grupos que importam
O HAVING filtra os grupos depois de formados, usando suas agregações: departamentos com mais de dez pessoas, clientes que gastaram mais de $1,000 ou valores que aparecem mais de uma vez, que é como você encontra duplicatas.
Exemplos reais de GROUP BY
Contar os produtos de cada categoria, inclusive as vazias
O GROUP BY só cria grupos para valores que existem. Para listar também as categorias sem produtos, comece pela tabela de categorias e faça um LEFT JOIN com os produtos. Conte uma coluna da tabela unida em vez de *: uma categoria vazia ainda recebe uma linha, preenchida com NULLs, e o COUNT(*) a conta.
-- Input: categories
-- category_name
-- -------------
-- Audio
-- Cameras
-- Drones
-- Input: products
-- id | name | category
-- ---+------------+---------
-- 1 | Earbuds | Audio
-- 2 | Soundbar | Audio
-- 3 | Action Cam | Cameras
-- Wrong: COUNT(*) counts the NULL row of an empty category
SELECT c.category_name, COUNT(*) AS product_count
FROM categories c
LEFT JOIN products p ON p.category = c.category_name
GROUP BY c.category_name
ORDER BY c.category_name;
-- Output:
-- category_name | product_count
-- --------------+--------------
-- Audio | 2
-- Cameras | 1
-- Drones | 1
-- Right: COUNT(p.id) skips it
SELECT c.category_name, COUNT(p.id) AS product_count
FROM categories c
LEFT JOIN products p ON p.category = c.category_name
GROUP BY c.category_name
ORDER BY c.category_name;
-- Output:
-- category_name | product_count
-- --------------+--------------
-- Audio | 2
-- Cameras | 1
-- Drones | 0
Encontrar as três categorias com maior receita
Some as vendas de cada categoria, ordene pelo total e fique com as três primeiras. Books fica em segundo, embora cada uma de suas vendas seja menor que a única venda de Toys:
-- Input: purchases
-- category | amount
-- ---------+-------
-- Books | 45.00
-- Toys | 120.00
-- Garden | 310.00
-- Books | 80.00
-- Music | 25.00
SELECT category, SUM(amount) AS revenue
FROM purchases
GROUP BY category
ORDER BY revenue DESC
LIMIT 3;
-- SQL Server has no LIMIT: write SELECT TOP 3 category, ... instead
-- Output:
-- category | revenue
-- ---------+--------
-- Garden | 310.00
-- Books | 125.00
-- Toys | 120.00
Relatório de vendas por região por mês
Agrupe pela região e pelo mês extraído da data, para que cada região tenha uma linha por mês:
-- Input: sales
-- region | sale_date | amount
-- -------+------------+-------
-- East | 2024-01-08 | 120.00
-- West | 2024-01-15 | 80.00
-- East | 2024-01-22 | 95.00
-- East | 2024-02-03 | 60.00
-- West | 2024-02-11 | 150.00
SELECT region, EXTRACT(MONTH FROM sale_date) AS month, SUM(amount) AS total_sales
FROM sales
GROUP BY region, EXTRACT(MONTH FROM sale_date)
ORDER BY region, month;
-- Output:
-- region | month | total_sales
-- -------+-------+------------
-- East | 1 | 215.00
-- East | 2 | 60.00
-- West | 1 | 80.00
-- West | 2 | 150.00
SQL Server e SQLite não têm EXTRACT. Use MONTH(sale_date) no SQL Server e strftime('%m', sale_date) no SQLite. Agrupe também pelo ano quando seus dados abrangerem mais de um.
Encontrar clientes recorrentes neste ano
O WHERE mantém só os pedidos de 2024, e depois o HAVING mantém os clientes com pelo menos dois deles. O cliente 202 tem dois pedidos no total, mas só um em 2024, então fica de fora:
-- Input: orders
-- order_id | customer_id | order_date
-- ---------+-------------+-----------
-- 1 | 201 | 2023-11-20
-- 2 | 202 | 2023-12-05
-- 3 | 201 | 2024-01-09
-- 4 | 202 | 2024-02-14
-- 5 | 201 | 2024-03-02
SELECT customer_id, COUNT(*) AS orders_2024
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id
HAVING COUNT(*) >= 2;
-- Output:
-- customer_id | orders_2024
-- ------------+------------
-- 201 | 2
Leitura complementar sobre GROUP BY
Neste blog:
- Guia de SQL COUNT DISTINCT: contando valores únicos em cada grupo
-
Guia para encontrar duplicatas em SQL: GROUP BY com
HAVING COUNT(*) > 1 - Guia de SQL ORDER BY: ordenando os resultados agrupados
- Guia de SQL CASE: contagens e somas condicionais dentro de uma agregação
- Guia de tabela dinâmica (pivot) em SQL: transformando linhas agrupadas em colunas
- Guia de SQL COALESCE: rotulando o grupo NULL
- Guia de SQL JOIN com diagramas: o LEFT JOIN que mantém grupos vazios
Documentação oficial:
- PostgreSQL: GROUP BY e HAVING
- MySQL: tratamento do GROUP BY
- SQLite: SELECT (consultas de agregação)
- SQL Server: GROUP BY
Beekeeper Studio é um cliente de banco de dados gratuito e open source
A melhor ferramenta de consultas e edição de SQL que já usei. Tem tudo o que preciso para gerenciar meu banco de dados. - ⭐⭐⭐⭐⭐ Mit
O Beekeeper Studio é rápido, intuitivo e fácil de usar. Ele suporta vários bancos de dados e funciona muito bem no Windows, Mac e Linux.
O que os usuários dizem sobre o Beekeeper Studio
"O Beekeeper Studio substituiu completamente meu antigo fluxo de trabalho com SQL. É rápido, intuitivo e torna o trabalho com bancos de dados agradável de novo."
"Já testei muitas GUIs de banco de dados, mas o Beekeeper tem o equilíbrio perfeito entre recursos e simplicidade. Simplesmente funciona."