Contenido de esta guía
SQL se escribe en un orden y se razona en otro
Para una consulta de selección con agrupaciones, la secuencia lógica básica es FROM y JOIN → WHERE → GROUP BY y agregaciones → HAVING → SELECT → DISTINCT → ORDER BY → límite de filas. Las funciones de ventana trabajan con las filas o grupos que quedan después de WHERE, GROUP BY y HAVING.
Este orden describe el significado del resultado y qué datos están disponibles en cada fase. No es una cronología garantizada del trabajo del servidor: el optimizador puede elegir otro plan físico equivalente. La documentación de SELECT en SQL Server distingue ambos conceptos.
Qué hace cada cláusula
| Etapa | Operación | Pregunta que responde |
|---|---|---|
| 1 | FROM, JOIN y ON | ¿De dónde salen las filas y cómo se relacionan? |
| 2 | WHERE | ¿Qué filas cumplen la condición antes de agrupar? |
| 3 | GROUP BY y agregaciones | ¿Qué grupos se forman y qué se calcula para cada uno? |
| 4 | HAVING | ¿Qué grupos cumplen la condición? |
| 5 | Ventanas y expresiones de SELECT | ¿Qué valores se calculan para las filas de salida? |
| 6 | DISTINCT | ¿Se eliminan filas de salida idénticas? |
| 7 | ORDER BY | ¿En qué orden se devuelve el resultado? |
| 8 | LIMIT, FETCH o TOP, según motor | ¿Qué parte del resultado se conserva? |
Es un esquema para un único bloque SELECT. Consultas con operaciones de conjuntos, subconsultas o cláusulas específicas de otros motores requieren considerar sus propios bloques. En un LEFT JOIN, las filas sin coincidencia se conservan antes del filtro WHERE; por eso mover una condición entre ON y WHERE puede cambiar el resultado.
El orden escrito habitual empieza por SELECT, seguido de FROM, WHERE, GROUP BY, HAVING y ORDER BY. Respeta esa sintaxis aunque expliques la consulta empezando por la tabla.
Ejemplo paso a paso: la categoría con más ventas pagadas
Queremos la categoría con mayor importe pagado, siempre que alcance al menos 100 unidades. Los importes de esta demostración son enteros para concentrarnos en las cláusulas. Ejecuta la preparación una sola vez en una conexión SQLite de pruebas.
CREATE TABLE ventas_orden_demo (
id INTEGER PRIMARY KEY,
categoria TEXT NOT NULL,
importe INTEGER NOT NULL,
estado TEXT NOT NULL
);
INSERT INTO ventas_orden_demo (id, categoria, importe, estado) VALUES
(1, 'Libros', 40, 'pagado'),
(2, 'Libros', 70, 'pagado'),
(3, 'Libros', 200, 'cancelado'),
(4, 'Cursos', 120, 'pagado'),
(5, 'Cursos', 30, 'pagado'),
(6, 'Material', 25, 'pagado');SELECT categoria, SUM(importe) AS total
FROM ventas_orden_demo
WHERE estado = 'pagado'
GROUP BY categoria
HAVING SUM(importe) >= 100
ORDER BY total DESC, categoria
LIMIT 1;1. FROM: empezamos con seis filas
La fuente es ventas_orden_demo. Cada fila representa una venta, no una categoría. La venta cancelada existe en los datos originales y debe descartarse antes de sumar.
2. WHERE: quedan cinco ventas pagadas
La fila con id = 3 desaparece del conjunto lógico porque su estado es cancelado. Sus 200 unidades no deben aumentar el total de Libros. Permanecen las filas 1, 2, 4, 5 y 6.
3. GROUP BY y SUM: se forman tres grupos
| categoria | SUM(importe) |
|---|---|
| Libros | 110 |
| Cursos | 150 |
| Material | 25 |
Las dos filas de Libros aportan 40 + 70. Las dos de Cursos aportan 120 + 30. Material solo aporta 25. La columna estado ya ha cumplido su función como filtro de las ventas individuales.
4. HAVING: sobreviven dos categorías
La condición se aplica al total de cada grupo. Cursos y Libros superan el umbral de 100; Material queda fuera. Usar WHERE importe >= 100 expresaría otra petición: eliminaría ventas pequeñas antes de calcular el total.
5. SELECT, ORDER BY y LIMIT: obtenemos una fila
La salida muestra categoria y la suma con el alias total. Se ordena primero por ese total de mayor a menor y después por categoría para resolver empates. El límite conserva una fila.
| categoria | total |
|---|---|
| Cursos | 150 |
Descargar las consultas del ejemplo
Por qué un alias de SELECT no sirve siempre en WHERE
En PostgreSQL y SQL Server, un alias creado en la lista SELECT no puede usarse como si fuera una columna de entrada del WHERE del mismo bloque. La forma portable es repetir la expresión o calcularla en una subconsulta o CTE y filtrar desde fuera. En ORDER BY sí puedes usar el alias de salida.
También hay diferencias entre dialectos: MySQL permite alias en HAVING; PostgreSQL admite algunos usos en GROUP BY; SQLite acepta extensiones de alias que no conviene trasladar sin comprobar. Para nuestro ejemplo usamos HAVING SUM(importe) >= 100, cuya intención queda explícita sin depender de esa extensión. Consulta la guía de alias que no funcionan en WHERE para corregir ese error.
Un alias tampoco convierte una agregación en una condición de fila. Si quieres comparar un total por categoría, usa HAVING o filtra el resultado agrupado desde un bloque exterior. Elegir WHERE o HAVING cambia qué estás filtrando, no solo cómo escribes el nombre.
Dónde entran ROW_NUMBER y otras funciones de ventana
Una función de ventana recibe las filas que han sobrevivido a los filtros y, si existe agrupación, a esa agrupación. Para conservar una posición calculada con ROW_NUMBER(), calcula primero la posición en un bloque interior y filtra después en el exterior. Ese es el patrón documentado por PostgreSQL para filtrar resultados de ventanas.
WITH clasificadas AS (
SELECT id, categoria, importe,
ROW_NUMBER() OVER (
PARTITION BY categoria
ORDER BY importe DESC, id
) AS posicion
FROM ventas_orden_demo
WHERE estado = 'pagado'
)
SELECT id, categoria, importe
FROM clasificadas
WHERE posicion = 1
ORDER BY categoria;| id | categoria | importe |
|---|---|---|
| 4 | Cursos | 120 |
| 2 | Libros | 70 |
| 6 | Material | 25 |
Aquí Material sí aparece: esta consulta busca una venta por categoría y no exige un total mínimo de 100. El filtro interior descarta las canceladas antes de numerar; la fila 3 no puede ganar en Libros. El desempate por id evita que dos importes iguales produzcan una selección ambigua.
Si numerases todas las ventas y solo después descartases las canceladas, la fila elegida para Libros podría ser la cancelada de 200; al quitarla, perderías la categoría. Esa diferencia se entiende siguiendo los dos bloques por separado.
Orden lógico, plan físico y diferencias entre motores
Para averiguar si una consulta usa índices, un recorrido de tabla o una ordenación, consulta su plan de ejecución. El esquema lógico no permite deducir cuántas veces se lee una tabla ni si se materializa una CTE. Tampoco garantiza que las condiciones de un WHERE se evalúen de izquierda a derecha.
No protejas una división suponiendo que denominador <> 0 siempre se evaluará antes que otra condición que divide. Expresa el cálculo de forma segura, por ejemplo con NULLIF, y revisa el resultado cuando el divisor sea cero.
| Motor | Límite de una fila | Ventanas |
|---|---|---|
| SQLite | LIMIT 1 | ROW_NUMBER disponible desde 3.25.0. |
| PostgreSQL | LIMIT 1 o FETCH FIRST 1 ROW ONLY | CTE y ROW_NUMBER admitidos. |
| MySQL 8.0+ | LIMIT 1 | CTE y ROW_NUMBER admitidos. |
| SQL Server | SELECT TOP (1) en la lista inicial | CTE y ROW_NUMBER admitidos; termina la sentencia previa con punto y coma. |
Para adaptar la primera consulta a SQL Server, escribe SELECT TOP (1) categoria, SUM(importe) AS total, conserva ORDER BY y elimina LIMIT. Los ejemplos descargables usan la sintaxis de SQLite. Si tu motor añade QUALIFY u otras cláusulas, consulta su orden lógico específico.
Ejercicio: devuelve todas las categorías que alcanzan 100
Mantén solo ventas pagadas. Muestra las categorías con total de al menos 100, ordenadas por total descendente y categoría. Antes de ejecutar, predice el número de filas y los totales.
Ver solución y resultado
SELECT categoria, SUM(importe) AS total
FROM ventas_orden_demo
WHERE estado = 'pagado'
GROUP BY categoria
HAVING SUM(importe) >= 100
ORDER BY total DESC, categoria;El resultado es Cursos, 150 y después Libros, 110. Quitamos LIMIT; WHERE sigue filtrando ventas y HAVING sigue filtrando grupos. No hay que cambiar el umbral ni moverlo de cláusula.
Preguntas frecuentes sobre el orden de SQL
¿SELECT se ejecuta antes que WHERE?
Se escribe antes, pero en este modelo lógico WHERE selecciona las filas antes de construir la salida de SELECT. Esto ayuda a entender el alcance de los alias; no describe el momento físico de cada cálculo.
¿GROUP BY ordena el resultado?
No debes depender de un orden observado. GROUP BY define grupos. Usa ORDER BY cuando necesites una salida ordenada y añade un desempate si vas a limitar filas.
¿HAVING exige escribir GROUP BY?
No siempre: una consulta agregada sin GROUP BY puede tratar el conjunto como un único grupo. Para decidir el filtro, distingue si tu condición depende de una fila o de una agregación.
Qué debes recordar
- Identifica la fuente, filtra filas, agrupa y filtra grupos antes de razonar sobre la salida.
- Los alias y las ventanas dependen del bloque donde se calculan.
- ORDER BY define el orden; un límite sin orden estable puede elegir filas distintas.
- El plan físico lo decide el motor y se inspecciona por separado.
Continúa aprendiendo
Fuentes y comprobación
La preparación, la consulta principal, la ventana y el ejercicio se han ejecutado en SQLite. Las diferencias de sintaxis se contrastan con la documentación de cada motor.