Skip to main content

ROW_NUMBER, RANK, DENSE_RANK: Ranking Rows

These three window functions assign a rank to each row within a partition. They differ only in how they handle ties.

FunctionTies get the same rank?Gaps after ties?
ROW_NUMBER()No - always unique-
RANK()YesYes
DENSE_RANK()YesNo
Prior lesson

This lesson builds on Window Functions. The sample dataset is defined in the Introduction to SQL lesson.

Syntax

ROW_NUMBER() OVER (PARTITION BY col ORDER BY col)
RANK() OVER (PARTITION BY col ORDER BY col)
DENSE_RANK() OVER (PARTITION BY col ORDER BY col)

ORDER BY inside OVER is required for all three.

Examples

Example 1: Compare all three on order amounts

SELECT
order_id,
customer_id,
total_amount,
ROW_NUMBER() OVER (ORDER BY total_amount DESC) AS row_num,
RANK() OVER (ORDER BY total_amount DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY total_amount DESC) AS dense_rnk
FROM orders;

Result:

order_idcustomer_idtotal_amountrow_numrnkdense_rnk
11999.00111
72999.00211
93999.00311
43796.00442
32349.00553
61349.00653
8589.97774
2159.98885
5449.90996
10424.9510107

Orders 1, 7, 9 all have 999.00. RANK gives all three rank 1 then jumps to 4. DENSE_RANK gives them rank 1 then continues at 2.

Example 2: Top order per customer (ROW_NUMBER pattern)

ROW_NUMBER is ideal for "keep only the first/last N rows per group" - a very common pattern.

WITH ranked_orders AS (
SELECT
order_id,
customer_id,
total_amount,
order_date,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY total_amount DESC
) AS rn
FROM orders
)
SELECT
order_id,
customer_id,
total_amount,
order_date
FROM ranked_orders
WHERE rn = 1;

Result - the single highest-value order per customer:

order_idcustomer_idtotal_amountorder_date
11999.002024-01-05
72999.002024-03-15
93999.002024-04-10
5449.902024-02-10
8589.972024-04-01

Common Mistake

ROW_NUMBER ties are broken arbitrarily

When two rows have the same ORDER BY value, ROW_NUMBER assigns different numbers - but which row gets 1 vs 2 is non-deterministic. Add a tiebreaker column (like order_id) to make the result stable.

ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY total_amount DESC, order_id ASC)

Practice

Write a query that ranks customers by their total spend (SUM(total_amount)) using DENSE_RANK. Return customer_id, total_spent, and spend_rank.

Show answer
WITH customer_totals AS (
SELECT
customer_id,
SUM(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
)
SELECT
customer_id,
total_spent,
DENSE_RANK() OVER (ORDER BY total_spent DESC) AS spend_rank
FROM customer_totals;

Expected result:

customer_idtotal_spentspend_rank
31795.001
11407.982
21348.003
589.974
474.855
Donate to this project