GROUP BY junta en grupos las filas que comparten un valor, para que funciones de agregación como COUNT, SUM y AVG devuelvan un resultado por grupo en lugar de uno para toda la tabla. Los ejemplos funcionan en PostgreSQL, MySQL, SQLite y SQL Server.
Cómo usar GROUP BY
Escribe después de GROUP BY la columna por la que quieres agrupar, y envuelve cada una de las demás columnas que seleccionas en una función de agregación: COUNT, SUM, AVG, MIN o MAX. Obtienes una fila por 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 filas se convierten en dos, una por equipo, y cada agregación solo ve las filas de ese equipo.
Reglas que debes conocer
- Cada columna seleccionada debe estar en GROUP BY o dentro de una agregación. Si no, no hay un único valor que mostrar para el grupo y la consulta falla:
-- 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)
Corrígelo agregando la columna a GROUP BY, o agregándola también, por ejemplo con MIN(name).
- Los NULL forman un solo grupo. Todas las filas con NULL en la columna de agrupación caen en el mismo grupo. Usa COALESCE para ponerle una etiqueta:
-- 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
-
WHERE filtra filas antes de agrupar; HAVING filtra grupos después. Solo HAVING puede evaluar una agregación como
COUNT(*) > 2. Todo lo que no necesite una agregación va en WHERE, así se agrupan menos filas. - Los grupos regresan sin un orden particular. SQLite y SQL Server, por ejemplo, muestran Blue antes que Red en la consulta de puntajes de arriba. Agrega ORDER BY siempre que el orden importe.
Sintaxis alternativa
Puedes agrupar por un alias de columna, o por la posición de la columna en la lista del SELECT, en lugar de repetir una expresión:
-- 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
ROLLUP agrega subtotales y un total general. Las filas de subtotal muestran NULL en las columnas que suman:
-- 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
Un GROUP BY sin agregación devuelve cada valor distinto una sola vez, igual que SELECT DISTINCT category FROM products.
¿Cuándo deberías usar GROUP BY?
Usa GROUP BY cada vez que una pregunta diga “por” o “para cada”: pedidos por cliente, ventas por mes, empleados por departamento. La mayoría de los usos entran en uno de estos cuatro.
Contar filas en cada grupo
Cuántos pedidos tiene cada estado, cuántos productos hay en cada categoría, cuántas personas se registraron cada día. COUNT(*) cuenta todas las filas del grupo, y COUNT(column) cuenta solo las filas donde esa columna no es NULL.
Total, promedio o rango de cada grupo
SUM, AVG, MIN y MAX convierten los valores de cada grupo en un solo número: ingresos por cliente, valor promedio de pedido, el producto más barato y el más caro de una categoría. Una sola consulta puede devolver todos los que necesites.
Desglosar resultados por más de una columna
Pon varias columnas después de GROUP BY para obtener una fila por cada combinación: ventas por región por mes, inventario por almacén por tipo de producto. Solo aparecen las combinaciones que existen en los datos.
Quedarte solo con los grupos que importan
HAVING filtra los grupos una vez formados, usando sus agregaciones: departamentos con más de diez personas, clientes que gastaron más de $1,000, o valores que aparecen más de una vez, que es como puedes encontrar duplicados.
Ejemplos reales de GROUP BY
Contar los productos de cada categoría, incluso las vacías
GROUP BY solo crea grupos para valores que existen. Para listar también las categorías sin productos, parte de la tabla de categorías y haz un LEFT JOIN con los productos. Cuenta una columna de la tabla unida en lugar de *: una categoría vacía sigue teniendo una fila, rellena con NULL, y COUNT(*) la cuenta.
-- 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 las tres categorías con más ingresos
Suma las ventas de cada categoría, ordena por el total y quédate con las tres primeras. Books queda en segundo lugar aunque cada una de sus ventas es menor que la única venta 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
Reportar ventas por región por mes
Agrupa por la región y por el mes extraído de la fecha, para que cada región tenga una fila por mes:
-- 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 y SQLite no tienen EXTRACT. Usa MONTH(sale_date) en SQL Server y strftime('%m', sale_date) en SQLite. Agrupa también por año cuando tus datos abarquen más de uno.
Encontrar clientes recurrentes de este año
WHERE deja solo los pedidos de 2024 y luego HAVING deja a los clientes con al menos dos de ellos. El cliente 202 tiene dos pedidos en total, pero solo uno en 2024, así que queda fuera:
-- 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
Más lecturas sobre GROUP BY
En este blog:
- Guía de SQL COUNT DISTINCT: contar valores únicos en cada grupo
-
Guía para encontrar duplicados en SQL: GROUP BY con
HAVING COUNT(*) > 1 - Guía de SQL ORDER BY: ordenar los resultados agrupados
- Guía de la sentencia SQL CASE: conteos y sumas condicionales dentro de una agregación
- Guía de tablas dinámicas en SQL: convertir filas agrupadas en columnas
- Guía de SQL COALESCE: etiquetar el grupo NULL
- Guía de SQL JOIN con diagramas: el LEFT JOIN que conserva los grupos vacíos
Documentación oficial:
- PostgreSQL: GROUP BY y HAVING
- MySQL: manejo de GROUP BY
- SQLite: SELECT (consultas de agregación)
- SQL Server: GROUP BY
Beekeeper Studio es un cliente de bases de datos gratuito y de código abierto
La mejor herramienta de consultas y edición de SQL que he usado. Tiene todo lo que necesito para administrar mi base de datos. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio es rápido, intuitivo y fácil de usar. Es compatible con muchísimas bases de datos y funciona de maravilla en Windows, Mac y Linux.
Lo que dicen los usuarios de Beekeeper Studio
"Beekeeper Studio reemplazó por completo mi antiguo flujo de trabajo con SQL. Es rápido, intuitivo y hace que trabajar con bases de datos vuelva a ser agradable."
"He probado muchas GUI de bases de datos, pero Beekeeper logra el equilibrio perfecto entre funciones y simplicidad. Simplemente funciona."