Consultas SQL · Razonamiento paso a paso

Orden de ejecución en SQL: de FROM al resultado

Sigue cómo cambian las filas y los grupos en una consulta. Entiende por qué un alias o una función no están disponibles en cualquier cláusula.

Actualizado: 29 septiembre 2026Ejemplos comprobados en SQLite
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

Orden lógico de una consulta SELECT sencilla
EtapaOperaciónPregunta que responde
1FROM, JOIN y ON¿De dónde salen las filas y cómo se relacionan?
2WHERE¿Qué filas cumplen la condición antes de agrupar?
3GROUP BY y agregaciones¿Qué grupos se forman y qué se calcula para cada uno?
4HAVING¿Qué grupos cumplen la condición?
5Ventanas y expresiones de SELECT¿Qué valores se calculan para las filas de salida?
6DISTINCT¿Se eliminan filas de salida idénticas?
7ORDER BY¿En qué orden se devuelve el resultado?
8LIMIT, 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.

SQLite · seis ventas ficticias
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');
SQLite · consulta completa
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

Totales después del filtro y la agrupación; orden ilustrativo
categoriaSUM(importe)
Libros110
Cursos150
Material25

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.

Resultado final de la consulta
categoriatotal
Cursos150

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.

SQLite · mayor venta pagada de cada categoría
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;
Resultado: una venta por categoría
idcategoriaimporte
4Cursos120
2Libros70
6Material25

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.

Adaptaciones de los ejemplos
MotorLímite de una filaVentanas
SQLiteLIMIT 1ROW_NUMBER disponible desde 3.25.0.
PostgreSQLLIMIT 1 o FETCH FIRST 1 ROW ONLYCTE y ROW_NUMBER admitidos.
MySQL 8.0+LIMIT 1CTE y ROW_NUMBER admitidos.
SQL ServerSELECT TOP (1) en la lista inicialCTE 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
SQLite · solución sin límite de una fila
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.