Error SQL · funciones de ventana

Una función de ventana no funciona en WHERE: calcula primero y filtra después

Las funciones de ventana necesitan el conjunto de filas que queda después de WHERE, GROUP BY y HAVING. Por eso no pueden decidir, en esa misma capa, qué filas superan WHERE. La solución habitual es calcular la ventana primero y filtrar después.

Lectura: 15–19 minEjemplo reproduciblePráctica con pistas

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

Consulta problemática
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

Subconsulta corregida
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

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

  1. Busca OVER(...) dentro de WHERE, HAVING o GROUP BY.
  2. Comprueba qué resultado necesitas filtrar: rango, número de fila, valor anterior, suma acumulada, etc.
  3. Mueve la función de ventana a SELECT en una subconsulta o CTE.
  4. Filtra el alias en la consulta exterior.
  5. 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

Intermedia · ventanas

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.

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: