ROW_NUMBER, RANK y DENSE_RANK
IntermedioLas tres numeran filas según un ORDER BY dentro de OVER, y solo se diferencian en los empates. row_number() da números distintos siempre. rank() da el mismo número a los empatados y salta los siguientes (1, 1, 3). dense_rank() da el mismo número sin saltos (1, 1, 2).
Sintaxis
row_number() OVER (PARTITION BY grupo ORDER BY criterio DESC, id)
rank() OVER (PARTITION BY grupo ORDER BY criterio DESC)
dense_rank() OVER (PARTITION BY grupo ORDER BY criterio DESC)Ejemplo
SELECT
r.name AS restaurante,
r.rating,
row_number() OVER (ORDER BY r.rating DESC, r.id) AS numero_fila,
rank() OVER (ORDER BY r.rating DESC) AS rank,
dense_rank() OVER (ORDER BY r.rating DESC) AS dense_rank
FROM restaurants AS r
WHERE r.city_id = 5 -- Bogotá
AND r.cuisine = 'helados'
AND r.rating IS NOT NULL
ORDER BY numero_fila;Resultado
| restaurante | rating | numero_fila | rank | dense_rank |
|---|---|---|---|---|
| Don Aguilar 105 | 4.6 | 1 | 1 | 1 |
| El Rincón Morales 342 | 4.6 | 2 | 1 | 1 |
| Sabor Jiménez 39 | 4.5 | 3 | 3 | 2 |
| La Esquina González 198 | 4.4 | 4 | 4 | 3 |
| Casa Villanueva 397 | 4.3 | 5 | 5 | 4 |
| Sabor Castro 317 | 3.6 | 6 | 6 | 5 |
| El Rincón Soto 338 | 3.6 | 7 | 6 | 5 |
Cómo leerlo
Las dos heladerías con 4.6 empatan. row_number las separa usando r.id como desempate, rank les da 1 a las dos y salta al 3, y dense_rank les da 1 y sigue con 2. Para «el mejor de cada grupo» sin empates se usa row_number; para «los que están en el primer puesto», incluidos los empates, rank o dense_rank.
Error común
Así no
SELECT r.name,
row_number() OVER (ORDER BY r.rating DESC) AS puesto
FROM restaurants AS r
WHERE r.city_id = 5;Usar row_number sin un desempate único en el ORDER BY. Si dos filas empatan, el motor puede darles el 1 y el 2 en cualquier orden, y la misma consulta puede devolver otro ganador en la siguiente ejecución. Agrega una columna única al final (ORDER BY r.rating DESC, r.id) o usa rank si los empates deben compartir puesto.
En otros motores: Snowflake, BigQuery
SnowflakeBigQuery
QUALIFYfiltra por el resultado de una función de ventana sin subconsulta (también existe en Databricks). PostgreSQL no lo tiene: calcula el ranking en una CTE y filtra conWHERE numero_fila = 1afuera. En ningún motor se puede usar una función de ventana en elWHERE.SELECT city_id, name, rating FROM restaurants QUALIFY row_number() OVER (PARTITION BY city_id ORDER BY rating DESC, id) = 1;
Estos fragmentos no se ejecutan en el curso: se muestran como referencia de cada motor.