Aprende SQL · lección gratuita
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.
GROUP BY a, b: produce una fila por cada combinación distinta de a y b. Es la base de los reportes cruzados (pivots).FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.SELECT se ejecuta tarde: por eso WHERE y GROUP BY no pueden usar los alias definidos en SELECT, pero ORDER BY sí (corre después).HAVING ve agregados, WHERE no: porque HAVING corre después de GROUP BY, cuando los agregados ya existen.LIMIT es lo último: recorta el resultado ya ordenado; por eso "el top 3" exige ORDER BY antes de LIMIT.Aunque escribas la consulta en este orden:
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...
el motor la procesa así:
FROM (y los JOIN): construye el conjunto de filas de origen.WHERE: descarta filas individuales (no puede usar agregados ni alias del SELECT).GROUP BY: agrupa las filas que sobrevivieron.HAVING: descarta grupos completos (sí puede usar agregados).SELECT: calcula las expresiones y alias de salida.ORDER BY: ordena el resultado (ya puede usar los alias del SELECT).LIMIT / OFFSET: recorta las primeras filas del resultado ordenado.Este orden explica los "porqués":
WHERE total > 5 falla si total es un alias o un agregado → el alias aún no existe y el agregado tampoco.ORDER BY revenue DESC funciona aunque revenue sea un alias → ORDER BY corre después de SELECT.ORDER BY ... LIMIT N; sin ORDER BY, LIMIT devuelve filas arbitrarias.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]
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
-- 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?".
| Paso | Cláusula | Puede usar |
|---|---|---|
| 1 | FROM / JOIN | columnas base |
| 2 | WHERE | columnas base (no agregados, no alias) |
| 3 | GROUP BY a, b | columnas base / expresiones |
| 4 | HAVING | agregados y columnas de grupo |
| 5 | SELECT | define alias y agregados |
| 6 | ORDER BY | alias del SELECT, agregados |
| 7 | LIMIT / OFFSET | recorta el resultado final |
---