GROUP BY
La cláusula GROUP BY agrupa filas que comparten un mismo valor en una o varias columnas y permite calcular valores agregados para cada grupo. Sin GROUP BY, las funciones de agregación como COUNT, SUM o AVG operan sobre todas las filas de la tabla a la vez. Con GROUP BY, operan sobre cada grupo por separado.
Sintaxis
SELECT columnas, funcion_agregacion(columna)
FROM tabla
WHERE condicion
GROUP BY columna1, columna2, ...
ORDER BY columna;GROUP BY se escribe después de WHERE y antes de ORDER BY. Las columnas que aparecen en el SELECT sin estar dentro de una función de agregación deben estar también en el GROUP BY.
Funciones de agregación
Antes de ver GROUP BY, conviene conocer las funciones de agregación principales:
| Función | Descripción |
|---|---|
COUNT(*) | Cuenta el número de filas |
COUNT(col) | Cuenta filas donde col no es NULL |
SUM(col) | Suma los valores de la columna |
AVG(col) | Calcula la media aritmética |
MIN(col) | Devuelve el valor mínimo |
MAX(col) | Devuelve el valor máximo |
Sin GROUP BY, estas funciones procesan todas las filas como un solo grupo:
SELECT
COUNT(*) AS total_productos,
AVG(precio) AS precio_medio,
MIN(precio) AS precio_minimo,
MAX(precio) AS precio_maximo,
SUM(stock) AS stock_total
FROM productos;| total_productos | precio_medio | precio_minimo | precio_maximo | stock_total |
|---|---|---|---|---|
| 30 | 339.6567 | 12.99 | 1899.99 | 2827 |
El resultado es una sola fila con las estadísticas de todos los productos. Ahora veamos cómo obtener estas mismas estadísticas desglosadas por categoría.
GROUP BY básico
Para contar cuántos productos hay en cada categoría:
SELECT categoria_id, COUNT(*) AS total_productos
FROM productos
GROUP BY categoria_id;| categoria_id | total_productos |
|---|---|
| 6 | 4 |
| 7 | 3 |
| 8 | 3 |
| 9 | 2 |
| 10 | 2 |
| 11 | 3 |
| 12 | 2 |
| 13 | 2 |
| 14 | 3 |
| 15 | 3 |
| 16 | 3 |
MySQL toma las 30 filas de productos, las agrupa por categoria_id y cuenta cuántas hay en cada grupo. La categoría 6 (Smartphones) tiene 4 productos, la 7 (Portátiles) tiene 3, y así sucesivamente.
GROUP BY con varias funciones de agregación
Puedes usar múltiples funciones de agregación en la misma consulta:
SELECT
categoria_id,
COUNT(*) AS total,
ROUND(AVG(precio), 2) AS precio_medio,
MIN(precio) AS mas_barato,
MAX(precio) AS mas_caro,
SUM(stock) AS stock_total
FROM productos
GROUP BY categoria_id
ORDER BY precio_medio DESC;| categoria_id | total | precio_medio | mas_barato | mas_caro | stock_total |
|---|---|---|---|---|---|
| 7 | 3 | 1616.00 | 1399.00 | 1899.99 | 55 |
| 6 | 4 | 874.74 | 599.99 | 1299.99 | 225 |
| 11 | 3 | 382.66 | 149.99 | 599.00 | 45 |
| 13 | 2 | 139.99 | 129.99 | 149.99 | 105 |
| 12 | 2 | 144.99 | 39.99 | 249.99 | 105 |
| 14 | 3 | 83.32 | 19.99 | 199.99 | 320 |
| 8 | 3 | 35.32 | 19.99 | 49.99 | 650 |
| 15 | 3 | 34.99 | 29.99 | 39.99 | 157 |
| 10 | 2 | 54.99 | 49.99 | 59.99 | 210 |
| 9 | 2 | 29.99 | 24.99 | 34.99 | 680 |
| 16 | 3 | 14.99 | 12.99 | 16.99 | 265 |
ROUND(AVG(precio), 2) redondea el precio medio a 2 decimales. Los portátiles (categoría 7) tienen el precio medio más alto con 1616 euros, mientras que las novelas (categoría 16) el más bajo con 14.99 euros.
GROUP BY con JOIN
En la consulta anterior, los IDs de categoría no son muy informativos. Podemos unir con la tabla categorias para mostrar el nombre:
SELECT
c.nombre AS categoria,
COUNT(*) AS total,
ROUND(AVG(p.precio), 2) AS precio_medio,
SUM(p.stock) AS stock_total
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
ORDER BY total DESC;| categoria | total | precio_medio | stock_total |
|---|---|---|---|
| Smartphones | 4 | 874.74 | 225 |
| Portátiles | 3 | 1616.00 | 55 |
| Accesorios electrónicos | 3 | 35.32 | 650 |
| Muebles | 3 | 382.66 | 45 |
| Fitness | 3 | 83.32 | 320 |
| Programación | 3 | 34.99 | 157 |
| Novelas | 3 | 14.99 | 265 |
| Camisetas | 2 | 29.99 | 680 |
| Pantalones | 2 | 54.99 | 210 |
| Cocina | 2 | 144.99 | 105 |
| Running | 2 | 139.99 | 105 |
Ahora vemos los nombres de categoría en lugar de IDs. El GROUP BY c.nombre agrupa por el nombre de la categoría.
GROUP BY con múltiples columnas
Puedes agrupar por más de una columna. MySQL crea un grupo para cada combinación única de valores:
SELECT
estado,
empleado_id,
COUNT(*) AS total_pedidos,
SUM(total) AS importe_total
FROM pedidos
GROUP BY estado, empleado_id
ORDER BY estado, empleado_id;| estado | empleado_id | total_pedidos | importe_total |
|---|---|---|---|
| cancelado | 4 | 1 | 699.00 |
| cancelado | 5 | 1 | 49.99 |
| cancelado | 6 | 1 | 149.99 |
| entregado | 4 | 3 | 3249.97 |
| entregado | 5 | 3 | 1109.95 |
| entregado | 6 | 1 | 449.98 |
| enviado | 4 | 2 | 1649.99 |
| enviado | 5 | 1 | 129.99 |
| enviado | 6 | 1 | 259.98 |
| pendiente | 4 | 2 | 99.98 |
| pendiente | 5 | 3 | 1643.97 |
| pendiente | 6 | 2 | 389.97 |
| procesando | 4 | 1 | 599.99 |
| procesando | 5 | 1 | 199.99 |
| procesando | 6 | 2 | 483.97 |
Cada fila representa una combinación única de estado y empleado. "entregado" con empleado 4 es un grupo diferente de "entregado" con empleado 5.
GROUP BY con WHERE
El WHERE filtra filas antes de la agrupación. Solo las filas que pasan el filtro se incluyen en los grupos:
SELECT
estado,
COUNT(*) AS total,
ROUND(AVG(total), 2) AS media_total
FROM pedidos
WHERE fecha_pedido >= '2025-12-01'
GROUP BY estado
ORDER BY total DESC;| estado | total | media_total |
|---|---|---|
| pendiente | 7 | 307.70 |
| procesando | 2 | 299.50 |
| cancelado | 2 | 374.50 |
| entregado | 1 | 74.97 |
| enviado | 1 | 249.99 |
Primero MySQL filtra para quedarse solo con pedidos desde diciembre de 2025, y después agrupa por estado. El WHERE reduce las filas antes de que GROUP BY actúe.
GROUP BY con expresiones
Puedes agrupar por el resultado de una expresión, no solo por columnas directas. Esto es útil para agrupar por periodos de tiempo:
SELECT
YEAR(fecha_pedido) AS anio,
MONTH(fecha_pedido) AS mes,
COUNT(*) AS total_pedidos,
SUM(total) AS ingresos
FROM pedidos
GROUP BY YEAR(fecha_pedido), MONTH(fecha_pedido)
ORDER BY anio, mes;| anio | mes | total_pedidos | ingresos |
|---|---|---|---|
| 2025 | 10 | 4 | 2794.92 |
| 2025 | 11 | 7 | 4553.89 |
| 2025 | 12 | 11 | 4820.87 |
| 2026 | 1 | 3 | 244.96 |
Agrupamos los pedidos por año y mes. Diciembre de 2025 fue el mes con más pedidos (11) y más ingresos.
GROUP BY y la regla de las columnas
En SQL estándar, toda columna en el SELECT que no esté dentro de una función de agregación debe aparecer en el GROUP BY. MySQL, por defecto en versiones recientes, aplica esta regla mediante el modo ONLY_FULL_GROUP_BY:
-- Esto da ERROR con ONLY_FULL_GROUP_BY activo
SELECT nombre, categoria_id, AVG(precio)
FROM productos
GROUP BY categoria_id;ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause
and contains nonaggregated column...
El error ocurre porque nombre no está en el GROUP BY ni dentro de una función de agregación. Si hay 4 smartphones agrupados, ¿qué nombre debería mostrar MySQL? No tiene una respuesta clara. Las soluciones son incluir nombre en el GROUP BY, quitarlo del SELECT, o usar una función de agregación como GROUP_CONCAT(nombre) o ANY_VALUE(nombre).
GROUP BY con SUM condicional
Una técnica avanzada es usar SUM con expresiones CASE o IF para contar subgrupos dentro de cada grupo:
SELECT
empleado_id,
COUNT(*) AS total,
SUM(estado = 'entregado') AS entregados,
SUM(estado = 'cancelado') AS cancelados,
SUM(estado IN ('pendiente', 'procesando', 'enviado')) AS activos
FROM pedidos
GROUP BY empleado_id;| empleado_id | total | entregados | cancelados | activos |
|---|---|---|---|---|
| 4 | 9 | 3 | 1 | 5 |
| 5 | 9 | 3 | 1 | 5 |
| 6 | 7 | 1 | 1 | 5 |
SUM(estado = 'entregado') funciona porque MySQL convierte TRUE a 1 y FALSE a 0, así que la suma cuenta cuántas veces la condición es verdadera.
Orden de procesamiento
Con GROUP BY, el orden completo de procesamiento es:
- FROM: identifica las tablas.
- WHERE: filtra filas individuales.
- GROUP BY: agrupa las filas restantes.
- SELECT: evalúa expresiones y agregaciones.
- ORDER BY: ordena el resultado.
- LIMIT: restringe el número de filas.
Observa que WHERE se ejecuta antes de GROUP BY. Si necesitas filtrar después de la agrupación (por ejemplo, "solo los grupos que tengan más de 3 productos"), necesitas HAVING, que veremos en el siguiente artículo.
Practica con GROUP BY
Usa el editor para experimentar con agrupaciones y funciones de agregación. Prueba a contar, sumar o promediar por distintas columnas:
Escrito por Eduardo Lázaro
