DENSE_RANK
DENSE_RANK asigna rangos a las filas dentro de su partición de forma similar a RANK, pero sin dejar huecos cuando hay empates. Si dos filas comparten el rango 2, la siguiente fila recibe el rango 3, no el 4. El nombre "dense" (denso) hace referencia precisamente a esta propiedad: la secuencia de rangos es compacta, sin saltos.
Esta función es especialmente útil cuando necesitas saber cuántos valores distintos están por encima de una fila determinada. Si un empleado tiene DENSE_RANK igual a 4, significa que hay exactamente 3 niveles salariales por encima del suyo, independientemente de cuántas personas estén en cada nivel.
Sintaxis
DENSE_RANK() OVER (
[PARTITION BY columna1, columna2, ...]
ORDER BY columna1 [ASC|DESC], columna2 [ASC|DESC], ...
)Igual que ROW_NUMBER y RANK, DENSE_RANK no recibe argumentos y se configura completamente a través de OVER. El ORDER BY es necesario para que los rangos tengan sentido.
Comportamiento básico
Compara directamente DENSE_RANK con RANK para apreciar la diferencia:
SELECT
nombre,
salario,
RANK() OVER (ORDER BY salario DESC) AS rank_val,
DENSE_RANK() OVER (ORDER BY salario DESC) AS dense_rank_val
FROM empleados;| nombre | salario | rank_val | dense_rank_val |
|---|---|---|---|
| Carlos López | 72000.00 | 1 | 1 |
| Ana García | 65000.00 | 2 | 2 |
| Marta Ruiz | 65000.00 | 2 | 2 |
| Roberto Fernández | 58000.00 | 4 | 3 |
| Laura Díaz | 52000.00 | 5 | 4 |
| David Moreno | 48000.00 | 6 | 5 |
| Elena Torres | 48000.00 | 6 | 5 |
| Pedro Sánchez | 43000.00 | 8 | 6 |
Observa que RANK salta del 2 al 4 después del empate de Ana y Marta, mientras que DENSE_RANK continúa con 3. Igualmente, después del empate de David y Elena en la posición 6 según RANK, Pedro es 8. Pero con DENSE_RANK, Pedro es 6. El rango máximo de DENSE_RANK (6) te dice que hay exactamente 6 niveles salariales distintos en la tabla.
Las tres funciones lado a lado
La comparación completa de las tres funciones de numeración aclara definitivamente cuándo usar cada una:
SELECT
nombre,
salario,
ROW_NUMBER() OVER (ORDER BY salario DESC) AS row_num,
RANK() OVER (ORDER BY salario DESC) AS rank_val,
DENSE_RANK() OVER (ORDER BY salario DESC) AS dense_val
FROM empleados
ORDER BY salario DESC;| nombre | salario | row_num | rank_val | dense_val |
|---|---|---|---|---|
| Carlos López | 72000.00 | 1 | 1 | 1 |
| Ana García | 65000.00 | 2 | 2 | 2 |
| Marta Ruiz | 65000.00 | 3 | 2 | 2 |
| Roberto Fernández | 58000.00 | 4 | 4 | 3 |
| Laura Díaz | 52000.00 | 5 | 5 | 4 |
| David Moreno | 48000.00 | 6 | 6 | 5 |
| Elena Torres | 48000.00 | 7 | 6 | 5 |
| Pedro Sánchez | 43000.00 | 8 | 8 | 6 |
ROW_NUMBER produce 1 a 8 sin repetir. RANK produce 1, 2, 2, 4, 5, 6, 6, 8 con huecos. DENSE_RANK produce 1, 2, 2, 3, 4, 5, 5, 6 sin huecos. La regla práctica es: ROW_NUMBER para identificadores únicos, RANK para clasificaciones tipo competición, y DENSE_RANK cuando los huecos distorsionarían el análisis.
Caso práctico: niveles salariales por departamento
DENSE_RANK brilla cuando necesitas identificar cuántos niveles distintos existen. Supón que Recursos Humanos quiere saber cuántas bandas salariales reales hay en cada departamento:
SELECT
nombre,
departamento,
salario,
DENSE_RANK() OVER (
PARTITION BY departamento
ORDER BY salario DESC
) AS banda_salarial
FROM empleados
ORDER BY departamento, banda_salarial;| nombre | departamento | salario | banda_salarial |
|---|---|---|---|
| Roberto Fernández | Marketing | 72000.00 | 1 |
| Laura Díaz | Marketing | 58000.00 | 2 |
| Javier Ruiz | Marketing | 58000.00 | 2 |
| Sofía Moreno | Marketing | 45000.00 | 3 |
| Carlos López | Ventas | 68000.00 | 1 |
| Ana García | Ventas | 62000.00 | 2 |
| Marta Ruiz | Ventas | 55000.00 | 3 |
| Pedro Sánchez | Ventas | 48000.00 | 4 |
Marketing tiene 3 bandas salariales distintas (a pesar de tener 4 empleados) mientras que Ventas tiene 4. El valor máximo de DENSE_RANK en cada departamento indica el número de niveles salariales distintos. Con RANK, el máximo sería 4 en Marketing debido al hueco, perdiendo esa información semántica.
Caso práctico: top N con empates incluidos
Una de las ventajas clave de DENSE_RANK sobre ROW_NUMBER para el patrón top-N es que incluye todos los empates. Si pides "los 3 mejores precios" y hay dos productos con el mismo tercer precio, DENSE_RANK los incluye a ambos:
WITH ranking_precios AS (
SELECT
nombre,
categoria,
precio,
DENSE_RANK() OVER (
PARTITION BY categoria
ORDER BY precio DESC
) AS dr
FROM productos
WHERE activo = 1
)
SELECT nombre, categoria, precio, dr AS nivel_precio
FROM ranking_precios
WHERE dr <= 3
ORDER BY categoria, dr;| nombre | categoria | precio | nivel_precio |
|---|---|---|---|
| Monitor 27" 4K | Electrónica | 349.00 | 1 |
| Auriculares Bluetooth Pro | Electrónica | 189.00 | 2 |
| Teclado mecánico RGB | Electrónica | 129.00 | 3 |
| Ratón gaming inalámbrico | Electrónica | 129.00 | 3 |
| Abrigo lana italiano | Ropa | 245.00 | 1 |
| Botas cuero premium | Ropa | 189.00 | 2 |
| Vestido seda natural | Ropa | 175.00 | 3 |
En Electrónica aparecen 4 productos porque el teclado y el ratón comparten el tercer nivel de precio. Con ROW_NUMBER, uno de los dos se habría quedado fuera arbitrariamente. Con RANK, el resultado sería idéntico a DENSE_RANK en este caso porque el filtro es <= 3. Sin embargo, si el empate estuviera en la posición 2 y filtraras por <= 3, RANK asignaría el siguiente como 4 y lo excluiría, mientras que DENSE_RANK le asignaría 3 y lo incluiría.
Caso práctico: encontrar el segundo valor más alto
Un problema frecuente es obtener el segundo valor más alto (o el tercero, cuarto, etc.) de cada grupo. DENSE_RANK garantiza que siempre exista un rango 2 si hay al menos dos valores distintos:
WITH salarios_rankeados AS (
SELECT
departamento,
salario,
DENSE_RANK() OVER (
PARTITION BY departamento
ORDER BY salario DESC
) AS dr
FROM empleados
)
SELECT DISTINCT departamento, salario AS segundo_salario_mas_alto
FROM salarios_rankeados
WHERE dr = 2;| departamento | segundo_salario_mas_alto |
|---|---|
| Marketing | 58000.00 |
| Ventas | 62000.00 |
| Soporte | 45000.00 |
Si usaras RANK y el primer salario estuviera empatado entre dos personas, el siguiente rango sería 3 y la consulta con WHERE dr = 2 no devolvería nada. DENSE_RANK siempre produce rangos consecutivos, así que el filtro dr = 2 siempre funciona si hay al menos dos valores distintos.
Manejo de NULL
El comportamiento de DENSE_RANK con NULL es el mismo que el de RANK: todos los valores NULL se consideran iguales y reciben el mismo rango:
SELECT
nombre,
bonus,
DENSE_RANK() OVER (ORDER BY bonus DESC) AS dr
FROM empleados;| nombre | bonus | dr |
|---|---|---|
| Carlos López | 8000.00 | 1 |
| Ana García | 5000.00 | 2 |
| Marta Ruiz | 5000.00 | 2 |
| Roberto Fernández | 3000.00 | 3 |
| Pedro Sánchez | NULL | 4 |
| Elena Torres | NULL | 4 |
Los NULL se agrupan en el mismo rango (4 en este caso, en orden descendente aparecen al final). El rango máximo sigue siendo la cuenta de valores distintos, incluyendo el grupo de NULL como uno más.
Combinación con otras funciones
DENSE_RANK es particularmente útil combinado con funciones de agregación para segmentar datos en niveles:
SELECT
nombre,
departamento,
salario,
DENSE_RANK() OVER (ORDER BY salario DESC) AS nivel_global,
CONCAT(
'Nivel ', DENSE_RANK() OVER (ORDER BY salario DESC),
' de ', (SELECT COUNT(DISTINCT salario) FROM empleados)
) AS etiqueta,
ROUND(
salario / MAX(salario) OVER () * 100, 1
) AS pct_del_maximo
FROM empleados
ORDER BY nivel_global;| nombre | departamento | salario | nivel_global | etiqueta | pct_del_maximo |
|---|---|---|---|---|---|
| Carlos López | Ventas | 72000.00 | 1 | Nivel 1 de 6 | 100.0 |
| Ana García | Ventas | 65000.00 | 2 | Nivel 2 de 6 | 90.3 |
| Marta Ruiz | Ventas | 65000.00 | 2 | Nivel 2 de 6 | 90.3 |
| Roberto Fernández | Marketing | 58000.00 | 3 | Nivel 3 de 6 | 80.6 |
| Laura Díaz | Marketing | 52000.00 | 4 | Nivel 4 de 6 | 72.2 |
| David Moreno | Soporte | 48000.00 | 5 | Nivel 5 de 6 | 66.7 |
| Elena Torres | Soporte | 48000.00 | 5 | Nivel 5 de 6 | 66.7 |
| Pedro Sánchez | Ventas | 43000.00 | 6 | Nivel 6 de 6 | 59.7 |
La etiqueta "Nivel 2 de 6" comunica de forma inmediata que este salario ocupa la segunda posición entre 6 niveles distintos, información que RANK distorsionaría al producir un máximo de 8 en lugar de 6.
Practica con DENSE_RANK
Usa el editor para asignar rankings consecutivos sin saltos:
En el siguiente artículo veremos NTILE para dividir filas en grupos iguales.
Escrito por Eduardo Lázaro
