⏳ ¡Últimos días de octubre! SQLtest.online es gratuito gracias a las donaciones, y aún necesitamos alcanzar la meta de este mes para que el sitio siga funcionando en noviembre. Cada donación cuenta. Apoyar ahora →
Código SQL copiado al portapapeles

ROW_NUMBER vs RANK vs DENSE_RANK vs NTILE

Lección 7.2 · Tiempo de lectura: ~8 min · Tareas de práctica ↓

Las funciones de ranking en SQL numeran filas en un orden definido: ROW_NUMBER, RANK y DENSE_RANK asignan posiciones, y NTILE divide las filas en grupos. Solo se diferencian en cómo tratan los empates, las filas con valores iguales, y esa diferencia decide si un informe es correcto. En esta lección las compararás todas con los mismos datos reales, verás por qué ROW_NUMBER necesita un criterio de desempate y sabrás qué función sirve para cada tarea.

En la lección anterior usaste ROW_NUMBER() para numerar pagos. Las funciones de ranking siguen el mismo patrón: una función, luego OVER con un ORDER BY que define qué significa "primero", y un PARTITION BY opcional que reinicia el ranking en cada grupo. Las funciones de ranking ignoran los marcos de ventana: siempre miran la partición completa.

Los ejemplos se ejecutan en la base de datos de ejemplo Sakila (MySQL 8.0).

ROW_NUMBER vs RANK vs DENSE_RANK: para los valores 9.99, 7.99, 5.99, 5.99, 4.99, ROW_NUMBER da 1 2 3 4 5, RANK da 1 2 3 3 5 y DENSE_RANK da 1 2 3 3 4

Las tres funciones lado a lado

El cliente 1 hizo 32 pagos. Varios son iguales: cuatro pagos de 5.99 y ocho de 4.99. Clasifiquemos los pagos de mayor a menor:

SELECT
    payment_id,
    amount,
    ROW_NUMBER() OVER (ORDER BY amount DESC, payment_id) AS row_num,
    RANK()       OVER (ORDER BY amount DESC) AS rnk,
    DENSE_RANK() OVER (ORDER BY amount DESC) AS dense_rnk
FROM payment
WHERE customer_id = 1
ORDER BY row_num
LIMIT 8;

Resultado: los 8 pagos más altos del cliente 1.

payment_idamountrow_numrnkdense_rnk
59.99111
147.99222
35.99333
105.99433
115.99533
325.99633
64.99774
74.99874
  • ROW_NUMBER le da a cada fila su propio número: 1, 2, 3, 4, … Los valores iguales reciben números distintos.
  • RANK da el mismo puesto a los valores iguales y luego salta números: cuatro pagos comparten el puesto 3, así que el siguiente es el 7. Es el "ranking de competición", como los puestos de una carrera.
  • DENSE_RANK da el mismo puesto a los valores iguales sin huecos: después del puesto 3 viene el 4. La cantidad de puestos distintos es igual a la cantidad de valores distintos.

ROW_NUMBER necesita un criterio de desempate

Entre valores iguales, ROW_NUMBER igual tiene que elegir un orden, y la base de datos puede elegir cualquiera. Sin payment_id en el ORDER BY de arriba, MySQL numeró los cuatro pagos de 5.99 como 4, 5, 6, 3, y otra ejecución u otra versión del servidor podría hacerlo distinto. Un informe que dependa de "la fila número 3" cambiaría entonces de una ejecución a otra.

Siempre que uses ROW_NUMBER para algo importante, como la paginación o "conservar la primera fila", agrega una columna única al ORDER BY dentro de OVER, por ejemplo la clave primaria. RANK y DENSE_RANK no lo necesitan: los valores iguales reciben de todos modos puestos iguales.


NTILE: dividir las filas en grupos

NTILE(n) no clasifica valores; reparte las filas ordenadas en n grupos de tamaño casi igual y devuelve el número del grupo. Es la forma estándar de construir cuartiles, deciles o segmentos de "el 10% superior".

Dividamos a los clientes de cada tienda en cuartiles de gasto:

WITH customer_total AS (
    SELECT c.store_id, c.customer_id, SUM(p.amount) AS total_paid
    FROM customer c
    JOIN payment p ON p.customer_id = c.customer_id
    GROUP BY c.store_id, c.customer_id
),
segmented AS (
    SELECT
        store_id,
        total_paid,
        NTILE(4) OVER (PARTITION BY store_id ORDER BY total_paid DESC) AS quartile
    FROM customer_total
)
SELECT
    store_id,
    quartile,
    COUNT(*) AS customers,
    MIN(total_paid) AS min_paid,
    MAX(total_paid) AS max_paid
FROM segmented
GROUP BY store_id, quartile
ORDER BY store_id, quartile;

Resultado: 8 filas; la tienda 1 tiene 326 clientes divididos en 82, 82, 81 y 81; su cuartil superior pagó de 128.73 a 216.54.

store_idquartilecustomersmin_paidmax_paid
1182128.73216.54
1282111.74128.73
138196.72111.73
148150.8595.78
2169127.68221.55
...............

Cuando las filas no se dividen en partes iguales, los primeros grupos reciben una fila más. NTILE divide por cantidad de filas, no por valor: los valores iguales en un límite pueden caer en grupos distintos, como 128.73 aquí.


PERCENT_RANK y CUME_DIST

Otras dos funciones expresan la posición como una fracción entre 0 y 1:

  • PERCENT_RANK() = (puesto − 1) / (filas − 1): la fracción de filas que están estrictamente por delante. La primera fila obtiene 0.
  • CUME_DIST() = filas con un valor hasta el actual, incluido / todas las filas: la fracción de filas en esa posición o por delante.
SELECT
    payment_id,
    amount,
    ROUND(PERCENT_RANK() OVER (ORDER BY amount DESC), 2) AS pct_rank,
    ROUND(CUME_DIST() OVER (ORDER BY amount DESC), 2) AS cumulative_share
FROM payment
WHERE customer_id = 1
ORDER BY amount DESC, payment_id
LIMIT 4;

Resultado: el pago de 9.99 tiene pct_rank 0 y cumulative_share 0.03; cada pago de 5.99 tiene pct_rank 0.06 y cumulative_share 0.19, porque 6 de los 32 pagos son de 5.99 o más.

Son útiles para filtros del tipo "el 10% de los mejores clientes": WHERE cumulative_share <= 0.1 en una consulta externa. No llames a la columna cume_dist ni rank: en MySQL 8 son palabras reservadas.


Qué función usar

TareaFunción
Exactamente N filas por grupo, última fila, paginaciónROW_NUMBER con criterio de desempate
Puestos en una competición: resultados iguales comparten puesto y el siguiente se saltaRANK
N-ésimo valor distinto, como el segundo salario más altoDENSE_RANK
Cuartiles, deciles, segmentos de igual tamañoNTILE(n)
Posición relativa en porcentajePERCENT_RANK, CUME_DIST

Dos ejercicios basados en estas funciones se tratan en detalle en las lecciones de ejercicios de entrevista: top N filas por grupo (lección 12.6) y el N-ésimo salario más alto (lección 12.4).


Conclusiones clave:

  • ROW_NUMBER, RANK y DENSE_RANK solo se diferencian en los empates: números únicos, puestos compartidos con huecos, puestos compartidos sin huecos.
  • Para los valores 9.99, 7.99, 5.99, 5.99, 4.99, devuelven 1-2-3-4-5, 1-2-3-3-5 y 1-2-3-3-4.
  • Con ROW_NUMBER, agrega un criterio de desempate único al ORDER BY, o la numeración de los valores iguales será arbitraria.
  • NTILE(n) divide las filas ordenadas en n grupos casi iguales según la cantidad de filas.
  • PERCENT_RANK y CUME_DIST dan la posición como una fracción entre 0 y 1.

Preguntas frecuentes

¿Cuál es la diferencia entre ROW_NUMBER, RANK y DENSE_RANK?

Las tres numeran las filas según el ORDER BY de OVER. ROW_NUMBER siempre da números únicos. RANK da el mismo puesto a los valores iguales y salta los números siguientes (1, 2, 2, 4). DENSE_RANK da el mismo puesto a los valores iguales sin saltar números (1, 2, 2, 3).

¿Cuándo debo usar RANK en lugar de DENSE_RANK?

Usa RANK cuando la posición deba reflejar cuántas filas hay por delante, como en los resultados deportivos: dos ganadores comparten el 1.er puesto y el siguiente es el 3.º. Usa DENSE_RANK cuando necesites niveles consecutivos de valores distintos, por ejemplo "el segundo precio más alto".

¿Cómo maneja NTILE las filas que no se dividen en partes iguales?

Los primeros grupos reciben una fila extra. Con 326 filas y NTILE(4), los grupos tienen 82, 82, 81 y 81 filas. NTILE cuenta filas, así que los valores iguales pueden quedar repartidos entre dos grupos vecinos.

¿Por qué ROW_NUMBER da resultados distintos para la misma consulta?

Porque el ORDER BY dentro de OVER tiene empates, y la base de datos puede ordenar las filas iguales de forma distinta en cada ejecución. Agrega una columna única, como la clave primaria, como última clave de orden para que la numeración sea determinista.

Intenta resolver las siguientes tareas para reforzar lo que aprendiste en esta lección.

  1. Clasificación de Sueldos
  2. Categorías de Peso de Productos
  3. Película Más Popular
  4. Detalles del Último Cliente que Alquiló
  5. Discos con el Mayor Costo de Reemplazo