Aprende SQL · lección gratuita

Lección 16 · Agrupación múltiple y orden de ejecución

Resumen

Esta lección cierra el módulo con dos ideas que separan a un usuario casual de uno avanzado: agrupar por varias columnas a la vez (para abrir un total en sus dimensiones: canal × estado, segmento × país) y entender el orden de ejecución real de una consulta. SQL se escribe empezando por SELECT, pero el motor lo ejecuta en otro orden, y conocerlo explica por qué WHERE no ve alias, por qué HAVING sí ve agregados y por qué LIMIT se aplica al final.

Sintaxis / Conceptos

El orden de ejecución real

Aunque escribas la consulta en este orden:

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...

el motor la procesa así:

  1. FROM (y los JOIN): construye el conjunto de filas de origen.
  2. WHERE: descarta filas individuales (no puede usar agregados ni alias del SELECT).
  3. GROUP BY: agrupa las filas que sobrevivieron.
  4. HAVING: descarta grupos completos (sí puede usar agregados).
  5. SELECT: calcula las expresiones y alias de salida.
  6. ORDER BY: ordena el resultado (ya puede usar los alias del SELECT).
  7. LIMIT / OFFSET: recorta las primeras filas del resultado ordenado.

Este orden explica los "porqués":

Míralo en acción, paso a paso, con los datos reales del curso:

[FLOWMAP:SELECT e.nombre, 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 WHERE p.estado <> 'Cancelado' GROUP BY e.id HAVING SUM(d.cantidad d.precio_unitario (1 - d.descuento)) > 200000 ORDER BY revenue DESC LIMIT 3]

Agrupación por varias columnas

GROUP BY canal, estado no agrupa "primero por canal y luego por estado" como jerarquía visual: forma un grupo por cada par único (canal, estado) que exista en los datos. Si un par no aparece, no hay fila para él. Es el equivalente SQL de una tabla cruzada.

SELECT estado, COUNT(*) AS pedidos, SUM(cantidad) AS unidades FROM pedidos JOIN detalle_pedidos ON pedidos.id = detalle_pedidos.pedido_id GROUP BY estado

Ejemplos

-- Reporte cruzado: pedidos por canal y estado (combinaciones existentes).
SELECT canal, estado, COUNT(*) AS num_pedidos
FROM pedidos
GROUP BY canal, estado
ORDER BY canal, estado;

-- Clientes por segmento y país (dos dimensiones a la vez).
SELECT segmento, pais, COUNT(*) AS num_clientes
FROM clientes
GROUP BY segmento, pais
ORDER BY segmento, num_clientes DESC, pais;

-- TODAS las cláusulas juntas + orden de ejecución en acción:
-- top 3 vendedores por revenue, contando solo pedidos NO cancelados,
-- y quedándonos solo con quienes superan 200000.
SELECT
  e.nombre,
  ROUND(SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)), 2) AS revenue
FROM detalle_pedidos d                       -- 1. FROM + JOIN
JOIN pedidos p   ON d.pedido_id  = p.id
JOIN empleados e ON p.empleado_id = e.id
WHERE p.estado <> 'Cancelado'                -- 2. WHERE (por fila)
GROUP BY e.id                                -- 3. GROUP BY
HAVING SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)) > 200000  -- 4. HAVING (grupos)
ORDER BY revenue DESC                        -- 6. ORDER BY (usa el alias)
LIMIT 3;                                      -- 7. LIMIT

-- Revenue por categoría y año: dimensión de catálogo × tiempo.
SELECT
  c.nombre AS categoria,
  strftime('%Y', p.fecha) AS anio,
  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 productos pr ON d.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
GROUP BY c.id, anio
ORDER BY categoria, anio;
💡 Memoriza la cadena FWGHSOL (FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT). Resuelve casi todas las dudas de "¿por qué este alias no funciona aquí?" o "¿por qué mi LIMIT trae filas raras?".

Cheatsheet

PasoCláusulaPuede usar
1FROM / JOINcolumnas base
2WHEREcolumnas base (no agregados, no alias)
3GROUP BY a, bcolumnas base / expresiones
4HAVINGagregados y columnas de grupo
5SELECTdefine alias y agregados
6ORDER BYalias del SELECT, agregados
7LIMIT / OFFSETrecorta el resultado final

---

← Filtrar grupos: HAVINGCombinar tablas: INNER JOIN →

Ver todas las lecciones de Aprende SQL →