ROW_NUMBER, RANK and DENSE_RANK: number and sort rows
Sorting a result does not assign a position that we can reuse. If we need to choose each customer’s most recent order, number results, or represent ties, we use sorting functions.
All three functions work with OVER, the clause that defines the rows and order used by the calculation. The difference appears when several rows share the same logical position.
The idea
ROW_NUMBER: assign a different number to each row
ROW_NUMBER() OVER
(
PARTITION BY ColumnaDeGrupo
ORDER BY ColumnaDeOrden
)
RANK: preserve ties and leave gaps
RANK() OVER
(
PARTITION BY ColumnaDeGrupo
ORDER BY ColumnaDeOrden
)
DENSE_RANK: preserve ties without leaving gaps
DENSE_RANK() OVER
(
PARTITION BY ColumnaDeGrupo
ORDER BY ColumnaDeOrden
)
Example: most recent order for each customer
;WITH PedidosNumerados AS
(
SELECT
p.IdPedido,
p.IdCliente,
p.FechaPedido,
p.Estado,
ROW_NUMBER() OVER
(
PARTITION BY p.IdCliente
ORDER BY p.FechaPedido DESC, p.IdPedido DESC
) AS Numero
FROM dbo.Pedido AS p
)
SELECT
IdPedido,
IdCliente,
FechaPedido,
Estado
FROM PedidosNumerados
WHERE Numero = 1;
Each client starts their own numbering. Descending order places the most recent order first and IdPedido resolves a possible date tie.
Compare the treatment of ties
SELECT
p.Nombre,
p.Precio,
ROW_NUMBER() OVER (ORDER BY p.Precio DESC) AS Numero,
RANK() OVER (ORDER BY p.Precio DESC) AS Posicion,
DENSE_RANK() OVER (ORDER BY p.Precio DESC) AS PosicionDensa
FROM dbo.Producto AS p;
If the ordered prices were 800, 800 and 600, we would obtain:
| Price | ROW_NUMBER |
RANK |
DENSE_RANK |
|---|---|---|---|
| 800 | 1 | 1 | 1 |
| 800 | 2 | 1 | 1 |
| 600 | 3 | 3 | 2 |
ROW_NUMBER physically separates tied rows. RANK skips position 2. DENSE_RANK continues without gaps.
Choose a function according to the need
To select exactly one row per customer we need ROW_NUMBER with a complete tiebreaker. For a leaderboard where two products can share a spot, RANK or DENSE_RANK usually better represents the intent.
What should be checked
If the ORDER BY of ROW_NUMBER does not distinguish every row, the assignment among ties may change from one execution to another. Adding a stable key—a unique value that does not change between executions—at the end of the order makes the choice deterministic: the same set produces the same selected row.
The ORDER BY inside OVER controls the sorting, but does not guarantee the visible order of the result. The outer query needs its own ORDER BY when the presentation must preserve a sequence.