El síntoma: ROW_NUMBER, RANK o LAG aparece dentro de WHERE
PostgreSQL, MySQL y SQLite limitan las funciones de ventana a la lista de resultados y a ORDER BY. PostgreSQL y MySQL explican además el motivo lógico: el filtrado con WHERE ocurre antes del procesamiento de ventanas.
Eso significa que no puedes calcular ROW_NUMBER() y usar ese mismo resultado en WHERE dentro del mismo nivel de consulta.
Ejemplo: intentar quedarse con la venta más reciente de cada cliente
SELECT cliente_id,
fecha,
ROW_NUMBER() OVER (
PARTITION BY cliente_id
ORDER BY fecha DESC, id DESC
) AS posicion
FROM pedidos
WHERE ROW_NUMBER() OVER (
PARTITION BY cliente_id
ORDER BY fecha DESC, id DESC
) = 1;La intención es razonable, pero la etapa de WHERE todavía no dispone del resultado de la ventana.
Calcula la ventana en una capa interior
SELECT cliente_id, fecha
FROM (
SELECT cliente_id,
fecha,
ROW_NUMBER() OVER (
PARTITION BY cliente_id
ORDER BY fecha DESC, id DESC
) AS posicion
FROM pedidos
) AS ordenados
WHERE posicion = 1;La consulta interior produce una tabla virtual que ya contiene posicion. En la consulta exterior, ese nombre es una columna normal y WHERE puede filtrarla.
La misma estrategia con una CTE
WITH ordenados AS (
SELECT cliente_id,
fecha,
ROW_NUMBER() OVER (
PARTITION BY cliente_id
ORDER BY fecha DESC, id DESC
) AS posicion
FROM pedidos
)
SELECT cliente_id, fecha
FROM ordenados
WHERE posicion = 1;La CTE no cambia la lógica: simplemente hace más visible la separación entre calcular y filtrar.
No olvides un ORDER BY determinista dentro de la ventana
Si varias filas pueden tener la misma fecha, añade un criterio de desempate estable, como id DESC. De lo contrario, la fila que recibe ROW_NUMBER() = 1 entre pares puede no representar una elección determinista.
Método de diagnóstico
- Busca OVER(...) dentro de WHERE, HAVING o GROUP BY.
- Comprueba qué resultado necesitas filtrar: rango, número de fila, valor anterior, suma acumulada, etc.
- Mueve la función de ventana a SELECT en una subconsulta o CTE.
- Filtra el alias en la consulta exterior.
- Revisa el ORDER BY de la ventana para que el cálculo represente exactamente la intención.
Error frecuente: sustituir WHERE por HAVING
HAVING tampoco es la capa de filtrado de ventanas
En PostgreSQL y MySQL, las funciones de ventana se calculan después de WHERE, GROUP BY y HAVING. Cambiar una cláusula por otra no resuelve el orden lógico. La capa intermedia sí lo hace.
Práctica correctiva
Devuelve los dos productos más caros de cada categoría
Usa ROW_NUMBER() particionando por categoria_id y ordenando por precio DESC, id. Después conserva solo posiciones 1 y 2.
ordenados.WHERE posicion <= 2.Solución razonada
WITH ordenados AS (
SELECT id,
categoria_id,
precio,
ROW_NUMBER() OVER (
PARTITION BY categoria_id
ORDER BY precio DESC, id
) AS posicion
FROM productos
)
SELECT id, categoria_id, precio
FROM ordenados
WHERE posicion <= 2;La CTE calcula primero la posición. El filtro exterior trabaja sobre un resultado que ya existe.
Qué debes recordar
- Las funciones de ventana no se filtran directamente en WHERE en estos motores.
- Calcula la ventana en una subconsulta o CTE y filtra fuera.
- HAVING no sustituye esa capa intermedia.
- Un ORDER BY estable dentro de OVER es parte del diagnóstico.
Conceptos relacionados
Fuentes técnicas
La sintaxis y las diferencias de motor de esta guía se contrastaron con documentación primaria: