Aprende SQL · lección gratuita

Lección 31 · Ranking: ROW_NUMBER, RANK, DENSE_RANK

Resumen

Las funciones de ranking numeran las filas según un orden definido en ORDER BY dentro de OVER. Son funciones de ventana puras (no agregan valores, solo asignan posiciones) y resuelven preguntas tipo "top N", "el más caro de cada categoría" o "posición dentro del grupo". Las tres más usadas difieren en cómo tratan los empates: ROW_NUMBER nunca empata, RANK deja huecos y DENSE_RANK no.

Sintaxis / Conceptos

ROW_NUMBER() OVER ([PARTITION BY ...] ORDER BY ...)
RANK()       OVER ([PARTITION BY ...] ORDER BY ...)
DENSE_RANK() OVER ([PARTITION BY ...] ORDER BY ...)

Diferencia con un ejemplo. Para salarios 12000, 8500, 8500, 8200:

SalarioROW_NUMBERRANKDENSE_RANK
12000111
8500222
8500322
8200443

ROW_NUMBER rompe el empate arbitrariamente (2 y 3), RANK repite el 2 y salta al 4, DENSE_RANK repite el 2 y sigue con el 3.

Top N por grupo. El patrón canónico es: rankear con PARTITION BY en una CTE y filtrar en el nivel externo. Si usas ROW_NUMBER obtienes exactamente N filas por grupo; si usas RANK/DENSE_RANK los empates en la posición N pueden traer filas extra (a veces es lo que quieres).

SELECT nombre, departamento_id, salario, RANK() OVER (PARTITION BY departamento_id ORDER BY salario DESC) AS rank_depto FROM empleados ORDER BY departamento_id, rank_depto LIMIT 8

Ejemplos

-- Las tres funciones lado a lado sobre el salario global.
-- Observa cómo difieren cuando hay valores cercanos o repetidos.
SELECT nombre,
       departamento_id,
       salario,
       ROW_NUMBER() OVER (ORDER BY salario DESC) AS fila,
       RANK()       OVER (ORDER BY salario DESC) AS rank_global,
       DENSE_RANK() OVER (ORDER BY salario DESC) AS dense_global
FROM empleados
ORDER BY salario DESC;
-- Ranking de salarios DENTRO de cada departamento (reinicia por partición).
SELECT departamento_id,
       nombre,
       salario,
       RANK() OVER (PARTITION BY departamento_id ORDER BY salario DESC) AS rank_depto
FROM empleados
ORDER BY departamento_id, rank_depto;
-- Top 2 mejor pagados de cada departamento.
-- Se rankea en la CTE y se filtra rn <= 2 en el nivel externo,
-- porque las window functions no pueden ir en WHERE.
WITH ranking AS (
  SELECT departamento_id,
         nombre,
         salario,
         ROW_NUMBER() OVER (PARTITION BY departamento_id
                            ORDER BY salario DESC) AS rn
  FROM empleados
)
SELECT departamento_id, nombre, salario
FROM ranking
WHERE rn <= 2
ORDER BY departamento_id, salario DESC;
💡 Para que ROW_NUMBER sea determinista ante empates, añade una columna desempatadora estable al ORDER BY, por ejemplo ORDER BY salario DESC, id. Si solo ordenas por una columna con duplicados, el orden de los empates puede variar entre ejecuciones.

Cheatsheet

FunciónEmpatesSaltosResultado típico
ROW_NUMBER()número únicon/a1, 2, 3, 4
RANK()mismo número1, 2, 2, 4
DENSE_RANK()mismo númerono1, 2, 2, 3
... OVER (PARTITION BY g ORDER BY x)ranking dentro de cada grupo g
CTE + WHERE rn <= Npatrón "top N por grupo"
ORDER BY x DESC, iddesempate determinista

---

← Funciones de ventana: OVER y PARTITION BYAcceso entre filas: LAG y LEAD →

Ver todas las lecciones de Aprende SQL →