Aprende SQL · lección gratuita

Lección 14 · Agrupar datos: GROUP BY

Resumen

GROUP BY reparte las filas en grupos según el valor de una o varias columnas, y luego aplica las funciones de agregación por grupo en vez de a toda la tabla. Es lo que convierte "el revenue total" en "el revenue de cada vendedor", o "cuántos pedidos hay" en "cuántos pedidos hay por estado". El resultado tiene una fila por grupo distinto.

Sintaxis / Conceptos

SELECT col_grupo, AGG(expr)
FROM tabla
[JOIN ...]
[WHERE filtro_de_filas]
GROUP BY col_grupo
[ORDER BY ...];

El WHERE filtra filas individuales antes de agrupar (no puede contener agregados). Es habitual agrupar por una clave (e.id) pero mostrar un nombre legible (e.nombre): mientras id sea único por grupo, incluir nombre en el SELECT es seguro porque hay un único nombre por grupo. Para máxima portabilidad, muchos lo añaden también al GROUP BY.

Puedes agrupar por una expresión, no solo por una columna: GROUP BY strftime('%Y-%m', fecha) agrupa por mes; GROUP BY categoria_id agrupa por categoría.

SELECT departamento_id, COUNT(*) AS empleados, ROUND(AVG(salario), 0) AS salario_prom FROM empleados GROUP BY departamento_id

Ejemplos

-- Revenue por vendedor: el clásico ranking comercial.
SELECT
  e.nombre,
  COUNT(DISTINCT p.id) AS pedidos,
  ROUND(SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)), 2) AS revenue
FROM detalle_pedidos d
JOIN pedidos p   ON d.pedido_id  = p.id
JOIN empleados e ON p.empleado_id = e.id
GROUP BY e.id
ORDER BY revenue DESC;

-- Conteo de pedidos por estado (cuántos hay de cada tipo).
SELECT estado, COUNT(*) AS num_pedidos
FROM pedidos
GROUP BY estado
ORDER BY num_pedidos DESC;

-- Salario promedio, mínimo y máximo por departamento (AVG con ROUND).
SELECT
  dp.nombre AS departamento,
  COUNT(*)  AS empleados,
  ROUND(AVG(e.salario), 2) AS salario_promedio,
  MIN(e.salario) AS minimo,
  MAX(e.salario) AS maximo
FROM empleados e
JOIN departamentos dp ON e.departamento_id = dp.id
GROUP BY dp.id
ORDER BY salario_promedio DESC;

-- Revenue por mes usando una expresión en el GROUP BY.
SELECT
  strftime('%Y-%m', p.fecha) AS mes,
  ROUND(SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)), 2) AS revenue
FROM detalle_pedidos d
JOIN pedidos p ON d.pedido_id = p.id
GROUP BY mes
ORDER BY mes;
💡 Error frecuente: poner una columna en el SELECT que no está agregada ni en el GROUP BY. Otros motores (PostgreSQL, MySQL estricto) lo rechazan; SQLite devuelve un valor arbitrario de la columna. Sé explícito: agrega o agrupa.

Cheatsheet

ElementoQué hace
GROUP BY colUna fila de salida por valor distinto de col
GROUP BY a, bUna fila por combinación distinta de a y b
GROUP BY exprAgrupa por una expresión (strftime, cálculo)
WHEREFiltra filas antes de agrupar (sin agregados)
Regla del SELECTNo agregado ⇒ debe estar en GROUP BY

---

← Funciones de agregación: COUNT, SUM, AVG, MIN, MAXFiltrar grupos: HAVING →

Ver todas las lecciones de Aprende SQL →