Al terminar, podrás
- Elegir qué filas forman cada partición de una función de ventana.
- Construir un ranking estable con
ROW_NUMBER()y un desempate explícito. - Usar una agregación con
OVERpara comparar cada fila con un total sin agruparla. - Calcular primero una ventana en una CTE y filtrar después cuando necesitas el primer elemento de cada grupo.
Recupera lo aprendido: ¿Dónde debe ir el filtro posicion = 1 cuando posicion se calcula con una ventana?
La idea clave: define primero la ventana y después el cálculo
En esta práctica trabajarás con la tabla pedidos. Para cada problema separa dos decisiones: qué filas pertenecen al mismo grupo y qué cálculo quieres hacer sobre ese grupo. Esa separación evita uno de los errores más frecuentes: escribir una función de ventana correcta sintácticamente pero con una partición u orden que no representa la pregunta.
Antes de mirar el número calculado, lee el PARTITION BY y el ORDER BY como una frase. Por ejemplo: «para cada cliente, ordena sus pedidos de mayor a menor total». Si esa frase no coincide con la pregunta, la ventana tampoco.
Ejemplo guiado: numera los pedidos de cada cliente
Queremos asignar la posición 1 al pedido de mayor importe de cada cliente. El identificador del pedido sirve como segundo criterio para que, si dos pedidos tienen el mismo total, la numeración siga siendo determinista.
SELECT cliente_id,
id AS pedido_id,
total,
ROW_NUMBER() OVER (
PARTITION BY cliente_id
ORDER BY total DESC, id
) AS posicion
FROM pedidos
ORDER BY cliente_id, posicion;El cliente 1 conserva sus tres pedidos y recibe las posiciones 1, 2 y 3: los pedidos 111, 101 y 103, respectivamente. El cliente 2 empieza otra vez en 1 porque PARTITION BY cliente_id reinicia la numeración para cada cliente.
Mantén el detalle mientras calculas un total por cliente
Una agregación tradicional con GROUP BY cliente_id devolvería una fila por cliente. Con una agregación de ventana puedes conservar cada pedido y añadir, en cada fila, el total acumulado de su cliente:
SELECT id AS pedido_id,
cliente_id,
total,
SUM(total) OVER (
PARTITION BY cliente_id
) AS total_cliente
FROM pedidos
ORDER BY cliente_id, id;Para el cliente 1 aparecen sus tres pedidos y, junto a cada uno, 402.3 como total_cliente. El cálculo resume el grupo, pero la consulta no reduce las tres filas a una sola. Esa diferencia es una de las razones principales para usar ventanas en informes analíticos.
Error habitual: filtrar la posición demasiado pronto
No uses la ventana directamente en WHERE en la misma capa
WHERE filtra antes de que se calculen las funciones de ventana en PostgreSQL, MySQL y SQLite. Si quieres quedarte con posicion = 1, calcula primero ROW_NUMBER() en una CTE o subconsulta y filtra en la consulta exterior.
Laboratorio: devuelve el pedido de mayor importe de cada cliente
Ahora integra CTE, ROW_NUMBER(), PARTITION BY y un filtro exterior. La consulta debe devolver una fila por cliente: el pedido con mayor total. En caso de empate, usa el menor id como desempate.
Laboratorio SQL
El pedido principal de cada cliente
Usa una CTE para calcular ROW_NUMBER() por cliente_id, ordenando los pedidos por total descendente e id ascendente como desempate. Incluye todos los estados. En la consulta exterior conserva posicion = 1 y devuelve cliente_id, pedido_id y total, ordenados por cliente_id.
Resultado esperado: 10 filas con cliente_id, pedido_id y total, ordenadas por cliente_id.
Ver tablas y datos
Cargando la estructura del ejercicio…
Dentro de la CTE, particiona por cliente_id.
Ordena la ventana con total DESC, id para que la posición 1 sea estable.
En la consulta exterior usa WHERE posicion = 1 y termina con ORDER BY cliente_id.
Solución
WITH ordenados AS (
SELECT cliente_id,
id AS pedido_id,
total,
ROW_NUMBER() OVER (
PARTITION BY cliente_id
ORDER BY total DESC, id
) AS posicion
FROM pedidos
)
SELECT cliente_id, pedido_id, total
FROM ordenados
WHERE posicion = 1
ORDER BY cliente_id;La CTE calcula la posición después de definir una ventana independiente para cada cliente. La consulta exterior ya puede filtrar esa columna calculada y conservar únicamente la fila con posición 1.
Puedes consultar la solución y volver a intentarlo. La práctica se completa cuando tu consulta devuelve el resultado pedido.
La consulta usa datos ficticios y se ejecuta solo en este navegador.
Ronda autónoma: detecta los dos pedidos más altos por cliente
Amplía el ranking sin cambiar la ventana
Reutiliza la consulta anterior para devolver como máximo los dos pedidos de mayor importe de cada cliente. Conserva también la columna posicion y ordena por cliente_id, posicion.
ROW_NUMBER() no necesita cambiar.WHERE posicion <= 2.Solución razonada
WITH ordenados AS (
SELECT cliente_id,
id AS pedido_id,
total,
ROW_NUMBER() OVER (
PARTITION BY cliente_id
ORDER BY total DESC, id
) AS posicion
FROM pedidos
)
SELECT cliente_id, pedido_id, total, posicion
FROM ordenados
WHERE posicion <= 2
ORDER BY cliente_id, posicion;La ventana sigue respondiendo a la misma pregunta: ordenar los pedidos dentro de cada cliente. Solo cambia el criterio con el que la consulta exterior decide cuántas posiciones conservar.
Si te bloqueas, separa la consulta en dos capas
- Escribe la consulta interior sin filtrar posiciones.
- Comprueba que cada cliente empieza en
1. - Revisa que el desempate esté dentro del
ORDER BYde la ventana. - Envuelve ese resultado en una CTE.
- Filtra
posiciondesde la consulta exterior.
Si todavía dudas sobre OVER o PARTITION BY, vuelve a la lección de funciones de ventana. Si lo que te bloquea es la consulta por etapas, repasa CTE con WITH. Para el caso específico de filtrar una ventana demasiado pronto, consulta función de ventana en WHERE.
Fuentes técnicas consultadas
La práctica utiliza sintaxis de ventana soportada por PostgreSQL, MySQL 8 y SQLite: OVER, PARTITION BY, orden dentro de la ventana y ROW_NUMBER(). Los tres motores mantienen el detalle de las filas al aplicar funciones de ventana.
Comprueba si puedes explicarlo
Si filtras los pedidos cancelados antes de ROW_NUMBER, ¿cambia el significado de «pedido mayor por cliente»?
Ver respuesta razonada
Sí. Obtienes el mayor entre los pedidos no cancelados. Si numeras primero y luego descartas cancelados, puedes perder un cliente cuyo pedido mayor estaba cancelado.
Responde antes de abrir la explicación. Si necesitaste ayuda, cierra la respuesta y vuelve a explicarlo con tus palabras antes de dar la lección por aprendida.
Qué debes recordar
PARTITION BYdefine dónde vuelve a empezar el cálculo.- El
ORDER BYdentro deOVERdetermina la posición, no necesariamente el orden final del resultado. - Una agregación con
OVERpuede resumir un grupo sin eliminar las filas individuales. - Para filtrar por una posición calculada, calcula la ventana en una capa y filtra en la siguiente.
A continuación: pasarás del análisis de filas a modificar datos con INSERT.