La función ROW_NUMBER() en SQL es una función de ventana que asigna un número de fila secuencial a cada registro dentro de un conjunto de resultados.
El contador comienza en 1 y se reinicia si se usa junto con PARTITION BY.
👉 Es ideal para generar rankings, numerar filas, obtener primeros registros por grupo o paginar resultados.
Sintaxis básica de ROW_NUMBER()
ROW_NUMBER() OVER (
[PARTITION BY columna]
ORDER BY columna
)
Elementos clave:
OVER: indica que es una función de ventana.PARTITION BY: divide los resultados en grupos (opcional).ORDER BY: define el orden en el que se asignan los números.
Ejemplo básico de ROW_NUMBER()
Tabla empleados:
| id | nombre | departamento | salario |
|---|---|---|---|
| 1 | Ana | Ventas | 3000 |
| 2 | Luis | Ventas | 2500 |
| 3 | Carla | Marketing | 2800 |
| 4 | Pedro | Marketing | 3500 |
Consulta:
SELECT nombre, departamento, salario,
ROW_NUMBER() OVER (ORDER BY salario DESC) AS posicion
FROM empleados;
Resultado:
| nombre | departamento | salario | posicion |
|---|---|---|---|
| Pedro | Marketing | 3500 | 1 |
| Ana | Ventas | 3000 | 2 |
| Carla | Marketing | 2800 | 3 |
| Luis | Ventas | 2500 | 4 |
👉 Numera las filas de mayor a menor salario.
ROW_NUMBER() con PARTITION BY
Podemos reiniciar la numeración por grupos.
SELECT nombre, departamento, salario,
ROW_NUMBER() OVER (
PARTITION BY departamento
ORDER BY salario DESC
) AS ranking
FROM empleados;
Resultado:
| nombre | departamento | salario | ranking |
|---|---|---|---|
| Ana | Ventas | 3000 | 1 |
| Luis | Ventas | 2500 | 2 |
| Pedro | Marketing | 3500 | 1 |
| Carla | Marketing | 2800 | 2 |
👉 Se reinicia la numeración en cada departamento.
Caso práctico: obtener el mejor empleado por departamento
Aquí ROW_NUMBER() se usa dentro de una subconsulta (ver más sobre SELECT y SUBQUERY):
SELECT nombre, departamento, salario
FROM (
SELECT nombre, departamento, salario,
ROW_NUMBER() OVER (
PARTITION BY departamento
ORDER BY salario DESC
) AS ranking
FROM empleados
) sub
WHERE ranking = 1;
👉 Devuelve el empleado con mayor salario en cada departamento.
ROW_NUMBER() vs RANK() vs DENSE_RANK()
Aunque similares, hay diferencias:
- ROW_NUMBER(): numera filas secuencialmente, sin importar empates.
- RANK(): asigna el mismo número en caso de empate, pero salta posiciones.
- DENSE_RANK(): asigna el mismo número en caso de empate, sin saltar posiciones.
Ejemplo:
SELECT nombre, salario,
ROW_NUMBER() OVER (ORDER BY salario DESC) AS rn,
RANK() OVER (ORDER BY salario DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY salario DESC) AS drnk
FROM empleados;
Resultado:
| nombre | salario | rn | rnk | drnk |
|---|---|---|---|---|
| Pedro | 3500 | 1 | 1 | 1 |
| Ana | 3000 | 2 | 2 | 2 |
| Carla | 2800 | 3 | 3 | 3 |
| Luis | 2500 | 4 | 4 | 4 |
Si Ana y Carla tuvieran el mismo salario:
- ROW_NUMBER() seguiría numerando secuencialmente (2, 3).
- RANK() asignaría (2, 2, 4).
- DENSE_RANK() asignaría (2, 2, 3).
ROW_NUMBER() para paginación de resultados
Muy útil para mostrar resultados por páginas en aplicaciones:
SELECT *
FROM (
SELECT id, nombre,
ROW_NUMBER() OVER (ORDER BY id) AS fila
FROM clientes
) sub
WHERE fila BETWEEN 11 AND 20;
👉 Devuelve los registros del 11 al 20.
Ejemplo con JOIN
ROW_NUMBER() también se puede combinar con JOIN para numerar registros relacionados, como pedidos de cada cliente.
SELECT c.nombre, p.total,
ROW_NUMBER() OVER (
PARTITION BY c.id
ORDER BY p.fecha DESC
) AS orden_pedido
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id;
👉 Numera los pedidos de cada cliente en orden cronológico.
Errores comunes con ROW_NUMBER()
- Olvidar el ORDER BY dentro de OVER
👉 SinORDER BY, el resultado no tiene un orden garantizado. - Confundir con GROUP BY
👉 ROW_NUMBER() no agrupa, conserva todas las filas. - Usar ROW_NUMBER() sin subconsulta para filtrar
👉 Para obtener “el primero de cada grupo”, hay que envolver en subconsulta. - Esperar consistencia sin índice
👉 El orden depende de la columna especificada y puede variar sin índices adecuados.
Buenas prácticas con ROW_NUMBER()
- Siempre definir un
ORDER BYclaro en la función. - Usar
PARTITION BYpara rankings por grupos. - En paginación, ordenar por una columna única (ej.
id). - Combinar con CTE (Common Table Expressions) para consultas más legibles.
- Evitar usarlo sin contexto: no es un sustituto de
LIMIT.
Casos de uso de ROW_NUMBER()
- Ranking de ventas por empleado.
- Primer/último registro por categoría.
- Detección de duplicados (filas con número > 1 se consideran repetidas).
- Paginar resultados en aplicaciones web.
- Análisis de series temporales (ordenando por fecha).
ROW_NUMBER() en distintos motores de bases de datos
- PostgreSQL: soporte completo.
- MySQL: desde versión 8 soporta funciones de ventana.
- SQL Server: soporte completo, muy usado para paginación.
- Oracle: disponible desde hace años.
- SQLite: lo soporta desde versión 3.25.
👉 Hoy en día, prácticamente todos los motores modernos soportan ROW_NUMBER().
Preguntas frecuentes (FAQ)
1. ¿ROW_NUMBER() comienza en 0 o en 1?
Siempre comienza en 1.
2. ¿Se puede usar ROW_NUMBER() sin ORDER BY?
Sí, pero el resultado no es determinístico (puede cambiar entre ejecuciones).
3. ¿Cuál es la diferencia con RANK()?
ROW_NUMBER() nunca repite números, incluso en empates.
4. ¿Puedo usar ROW_NUMBER() con múltiples columnas en ORDER BY?
Sí, por ejemplo:
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salario DESC, nombre ASC)
5. ¿Es costoso en rendimiento?
Depende del tamaño de la tabla y del ordenamiento. Conviene tener índices.
Conclusión
La función ROW_NUMBER() en SQL es fundamental para:
- Numerar filas de manera secuencial.
- Crear rankings dentro de grupos con
PARTITION BY. - Paginar resultados en aplicaciones reales.
- Resolver problemas de duplicados y obtener primeros registros.
Es una herramienta indispensable en análisis de datos y desarrollo de aplicaciones con bases de datos.
Aprende numeración de filas y orden de ventana en CASE y funciones de ventana y visita el Glosario SQL completo.