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ónDescripció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_productosprecio_medioprecio_minimoprecio_maximostock_total
30339.656712.991899.992827

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_idtotal_productos
64
73
83
92
102
113
122
132
143
153
163

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_idtotalprecio_mediomas_baratomas_carostock_total
731616.001399.001899.9955
64874.74599.991299.99225
113382.66149.99599.0045
132139.99129.99149.99105
122144.9939.99249.99105
14383.3219.99199.99320
8335.3219.9949.99650
15334.9929.9939.99157
10254.9949.9959.99210
9229.9924.9934.99680
16314.9912.9916.99265

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;
categoriatotalprecio_mediostock_total
Smartphones4874.74225
Portátiles31616.0055
Accesorios electrónicos335.32650
Muebles3382.6645
Fitness383.32320
Programación334.99157
Novelas314.99265
Camisetas229.99680
Pantalones254.99210
Cocina2144.99105
Running2139.99105

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;
estadoempleado_idtotal_pedidosimporte_total
cancelado41699.00
cancelado5149.99
cancelado61149.99
entregado433249.97
entregado531109.95
entregado61449.98
enviado421649.99
enviado51129.99
enviado61259.98
pendiente4299.98
pendiente531643.97
pendiente62389.97
procesando41599.99
procesando51199.99
procesando62483.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;
estadototalmedia_total
pendiente7307.70
procesando2299.50
cancelado2374.50
entregado174.97
enviado1249.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;
aniomestotal_pedidosingresos
20251042794.92
20251174553.89
202512114820.87
202613244.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_idtotalentregadoscanceladosactivos
49315
59315
67115

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:

  1. FROM: identifica las tablas.
  2. WHERE: filtra filas individuales.
  3. GROUP BY: agrupa las filas restantes.
  4. SELECT: evalúa expresiones y agregaciones.
  5. ORDER BY: ordena el resultado.
  6. 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:

Simulador SQL
Ctrl+Enter para ejecutar

Escrito por Eduardo Lázaro