1. Qué es
Una consulta se escribe en un orden y se ejecuta en otro. Ese desajuste explica la mitad de los errores de SQL, y una vez que lo tienes claro dejas de memorizar reglas sueltas.
Se escribe así:
SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
Se ejecuta así:
FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT
SELECT —lo primero que escribes— es lo penúltimo que ocurre. Todo lo demás se deduce de ahí.
Este orden es lógico, no físico. Es el orden en que el motor está obligado a comportarse como si hiciera las cosas. Por dentro puede reordenarlas todo lo que quiera mientras el resultado no cambie (§ 5 lo demuestra).
2. Los siete pasos, uno por uno
| # | Paso | Qué hace | Qué existe ya |
|---|---|---|---|
| 1 | FROM |
Trae las filas de la tabla (o las une, si hay JOIN) | Columnas de la tabla |
| 2 | WHERE |
Descarta filas, una por una | Columnas de la tabla |
| 3 | GROUP BY |
Colapsa las filas supervivientes en grupos | Columnas de la tabla |
| 4 | HAVING |
Descarta grupos | Columnas agrupadas + agregados |
| 5 | SELECT |
Calcula las columnas de salida y crea los alias | Todo lo anterior |
| 6 | DISTINCT |
Elimina filas de salida repetidas | Columnas de salida |
| 7 | ORDER BY |
Ordena el resultado | Alias incluidos |
| 8 | LIMIT |
Corta | El resultado ya ordenado |
De esa tabla salen, sin memorizar nada, las cuatro reglas que todo el mundo aprende a golpes:
WHEREno puede usar un alias delSELECT→ el alias todavía no existe (paso 5 > paso 2).WHEREno puede usar funciones de agregación → no hay grupos todavía (paso 3 > paso 2).ORDER BYsí puede usar un alias → se ejecuta después delSELECT.HAVINGfiltra grupos;WHEREfiltra filas.
3. Ejemplos sobre Northwind
Una consulta con todos los pasos. Países con más de 10 pedidos caros:
SELECT ship_country AS pais,
count(*) AS pedidos
FROM orders
WHERE freight > 50
GROUP BY pais
HAVING count(*) > 10
ORDER BY pedidos DESC
LIMIT 5;
pais | pedidos
---------+---------
USA | 61
Germany | 58
Austria | 33
Brazil | 32
France | 27
Ahora el embudo, medido de verdad, paso a paso:
| Paso | Qué queda |
|---|---|
1. FROM orders |
830 filas |
2. WHERE freight > 50 |
360 filas |
3. GROUP BY pais |
21 grupos |
4. HAVING count(*) > 10 |
10 grupos |
5-7. SELECT, ORDER BY, LIMIT 5 |
5 filas |
Cada número de esa tabla se puede comprobar por separado:
SELECT count(*) FROM orders; -- 830
SELECT count(*) FROM orders WHERE freight > 50; -- 360
El error clásico 1 — alias en el WHERE:
SELECT ship_country AS pais
FROM orders
WHERE pais = 'Peru';
ERROR: column "pais" does not exist
Cuando el WHERE se evalúa, pais no se ha inventado todavía. La solución es repetir la expresión: WHERE ship_country = 'Peru'.
El error clásico 2 — agregado en el WHERE:
SELECT ship_country, count(*)
FROM orders
WHERE count(*) > 10
GROUP BY ship_country;
ERROR: aggregate functions are not allowed in WHERE
Aquí PostgreSQL es explícito, y con razón: en el paso 2 cada fila está sola, no pertenece a ningún grupo, así que no hay nada que contar. Eso es exactamente para lo que existe HAVING.
La asimetría que sorprende a todo el mundo. GROUP BY sí acepta el alias:
SELECT ship_country AS pais, count(*) AS pedidos
FROM orders
GROUP BY pais -- ✅ funciona
HAVING pedidos > 10; -- ❌ ERROR: column "pedidos" does not exist
Si el orden lógico manda, GROUP BY (paso 3) tampoco debería ver el alias. Y en teoría no lo ve: es una comodidad que el estándar SQL concede solo a GROUP BY y a ORDER BY, no a WHERE ni a HAVING. La regla práctica es la que importa:
En
GROUP BYyORDER BYpuedes usar el alias. EnWHEREyHAVING, nunca.
ORDER BY también acepta el número de posición:
SELECT product_name, unit_price
FROM products
ORDER BY 2 DESC
LIMIT 3;
product_name | unit_price
-------------------------+------------
Côte de Blaye | 263.50
Thüringer Rostbratwurst | 123.79
Mishi Kobe Niku | 97.00
2 es la segunda columna del SELECT. Funciona porque ORDER BY ve el resultado ya construido. Cómodo para probar, frágil para guardar: el día que alguien añade una columna al SELECT, el orden cambia solo.
4. Errores comunes
- Poner en
HAVINGlo que va enWHERE. Filtrar por una columna de la tabla enHAVINGfunciona, pero es confuso de leer y solo es correcto si esa columna está en elGROUP BY. Si no, error. Filtra filas enWHERE, grupos enHAVING. - Esperar que
LIMITacelere una consulta lenta.LIMITes el paso 8. Antes de cortar, el motor ya escaneó, filtró, agrupó y ordenó todo.LIMIT 10sobre unORDER BYde un millón de filas no evita ordenar el millón. - Creer que
WHEREse evalúa de izquierda a derecha y protege al resto. No hay garantía de orden dentro delWHERE.WHERE x <> 0 AND 100/x > 5puede dar división por cero. Para eso estáCASE(case-condicionales-en-sql). - Confundir este orden con el rendimiento. Es un orden lógico, no una instrucción de ejecución. Ver abajo.
5. Orden lógico ≠ orden real
Se demuestra en 20 segundos. Estas dos consultas dan lo mismo (83 pedidos de Brasil), pero la segunda "debería" agrupar los 21 países y descartar 20 después:
SELECT ship_country, count(*) FROM orders WHERE ship_country = 'Brazil' GROUP BY ship_country;
SELECT ship_country, count(*) FROM orders GROUP BY ship_country HAVING ship_country = 'Brazil';
Pidiéndole al motor su plan con EXPLAIN, salen idénticos:
GroupAggregate (cost=0.00..25.59 rows=1 width=14)
-> Seq Scan on orders (cost=0.00..25.38 rows=83 width=6)
Filter: ((ship_country)::text = 'Brazil'::text)
El planificador empujó el filtro hacia abajo y filtró antes de agrupar, aunque escribieras HAVING. Puede hacerlo porque el resultado es demostrablemente el mismo.
Esto no es permiso para escribir mal. El motor optimiza los casos evidentes; los no evidentes se quedan como los escribiste. Y quien lea tu consulta no tiene un planificador dentro. Ver explain-y-explain-analyze.
6. Diferencias con SQL Server
El orden lógico es idéntico — es del estándar SQL, no del motor. Cambia solo el último paso: SQL Server usa TOP n (que va dentro del SELECT) u OFFSET ... FETCH, y PostgreSQL usa LIMIT.
Detalle de entrevista: en SQL Server, TOP sin ORDER BY no garantiza qué filas devuelve. En PostgreSQL, LIMIT sin ORDER BY tampoco. Es la misma trampa con distinto nombre.
7. Ejercicios
- Arregla esta consulta sin cambiar lo que devuelve:
sql SELECT unit_price * units_in_stock AS valor FROM products WHERE valor > 1000; - Arregla esta:
sql SELECT customer_id, count(*) FROM orders WHERE count(*) > 20 GROUP BY customer_id; - Sin ejecutarla, di cuántas filas devuelve y por qué:
sql SELECT ship_city FROM orders WHERE ship_country = 'Brazil' GROUP BY ship_city LIMIT 3; - Escribe la consulta que responde: empleados que gestionaron más de 100 pedidos, del que más al que menos. Usa las cinco cláusulas.
- Explica en una línea por qué esto no da error, aunque
precio_finalsea un alias:sql SELECT product_name, unit_price * 1.18 AS precio_final FROM products ORDER BY precio_final DESC;
8. Pregunta de negocio
Un compañero te enseña una consulta que tarda 40 segundos y te dice: "le puse
LIMIT 10, no entiendo por qué sigue tardando lo mismo". ¿Qué le respondes, y qué le propones mirar primero?