ROW_NUMBER, RANK & DENSE_RANK
Give rows a position with ROW_NUMBER, RANK and DENSE_RANK, understand exactly how each handles ties, and pick the right one.
"Who scored the highest innings?", "What is Kohli's rank?", "Show each player's best innings." All of these need a position number. SQL has three ranking functions that differ in one thing only: how they treat ties. Our innings table has three ties (73, 59 and 45 each appear twice), which makes the difference easy to see.
Definition
A ranking function assigns a position number to each row based on the ORDER BY inside OVER, restarting at 1 for every partition.
- ROW_NUMBER() gives every row a unique sequential number (1, 2, 3, 4, 5…) — even tied rows get different numbers.
- RANK() gives tied rows the same rank, then skips the following numbers — leaving a gap (1, 2, 3, 3, 5…).
- DENSE_RANK() gives tied rows the same rank and does not skip — no gap (1, 2, 3, 3, 4…).
- Position based on ORDER BY — the ORDER BY decides who is 1st. It is required.
- Restarting per partition — with PARTITION BY player, each player has their own 1, 2, 3.
- The brackets are always empty:
ROW_NUMBER(), notROW_NUMBER(runs). Frames are not allowed.
Syntax
sql
ROW_NUMBER() OVER ([PARTITION BY ...] ORDER BY sort_column [ASC | DESC])
RANK() OVER ([PARTITION BY ...] ORDER BY sort_column [ASC | DESC])
DENSE_RANK() OVER ([PARTITION BY ...] ORDER BY sort_column [ASC | DESC])Example — all three side by side
sql
SELECT id, player, runs,
ROW_NUMBER() OVER (ORDER BY runs DESC, id) AS row_no,
RANK() OVER (ORDER BY runs DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY runs DESC) AS dense_rnk
FROM innings
ORDER BY runs DESC, id;| id | player | runs | row_no | rnk | dense_rnk |
|---|---|---|---|---|---|
| 12 | Jos Buttler | 107 | 1 | 1 | 1 |
| 1 | Virat Kohli | 82 | 2 | 2 | 2 |
| 5 | Rohit Sharma | 76 | 3 | 3 | 3 |
| 3 | Virat Kohli | 73 | 4 | 4 | 4 |
| 10 | Jos Buttler | 73 | 5 | 4 | 4 |
| 2 | Virat Kohli | 59 | 6 | 6 | 5 |
| 11 | Jos Buttler | 59 | 7 | 6 | 5 |
| 4 | Rohit Sharma | 45 | 8 | 8 | 6 |
| 9 | MS Dhoni | 45 | 9 | 8 | 6 |
| 7 | MS Dhoni | 26 | 10 | 10 | 7 |
| 6 | Rohit Sharma | 0 | 11 | 11 | 8 |
| 8 | MS Dhoni | NULL | 12 | 12 | 9 |
How it works: (1) sort by runs, highest first; (2) number the rows.
- ROW_NUMBER: the two 73s get 4 and 5 — no ties ever.
- RANK: both 73s get 4, so the next score (59) jumps to 6 — rank 5 is skipped. RANK = "1 + number of rows strictly better than me".
- DENSE_RANK: both 73s get 4, and 59 gets 5 — no gap. DENSE_RANK = "1 + number of distinct better values".
- NULL: SQL Server treats NULL as the lowest value, so in
DESCorder Dhoni's NULL comes last (in ASC it would be first). Filter it out withWHERE runs IS NOT NULLif it should not be ranked.
Why
, idin ROW_NUMBER? When two rows tie on runs, the database may number them in any order, and the order can change between runs. Adding a unique tie-breaker (id) makes ROW_NUMBER deterministic — always the same answer.
Which ranking function should I use?
Ask two questions:
- Should tied values share a rank? → No →
ROW_NUMBER()(unique numbers — perfect for "exactly one row per group", pagination, removing duplicates). - Yes → Should the ranking leave gaps after ties?
- Yes →
RANK()— sports-style ranking ("two players joint 4th, next is 6th"). - No →
DENSE_RANK()— "top 3 distinct scores", salary bands, levels.
- Yes →
| Function | Ties share? | Gaps? | Sequence for 107, 82, 76, 73, 73, 59 |
|---|---|---|---|
| ROW_NUMBER | ❌ | — | 1, 2, 3, 4, 5, 6 |
| RANK | ✅ | ✅ | 1, 2, 3, 4, 4, 6 |
| DENSE_RANK | ✅ | ❌ | 1, 2, 3, 4, 4, 5 |
Example — Top-N analysis: best innings per player
sql
SELECT *
FROM (
SELECT id, player, runs,
ROW_NUMBER() OVER (PARTITION BY player ORDER BY runs DESC) AS rn
FROM innings
) t
WHERE rn = 1;| id | player | runs | rn |
|---|---|---|---|
| 12 | Jos Buttler | 107 | 1 |
| 1 | Virat Kohli | 82 | 1 |
| 5 | Rohit Sharma | 76 | 1 |
| 9 | MS Dhoni | 45 | 1 |
Step by step: (1) rank the innings inside each player (PARTITION BY player); (2) keep only rank 1 in the outer query. Window results cannot be used in WHERE of the same query, so we wrap them in a subquery (or a CTE). Change rn = 1 to rn <= 2 for each player's top 2.
Common mistakes
- ❌
WHERE ROW_NUMBER() OVER (...) = 1→ ✅ wrap in a subquery/CTE and filter outside. - ❌ Using ROW_NUMBER for "top 3 scores" when ties exist → a tied row is cut off arbitrarily ✅ use RANK or DENSE_RANK if ties must be included.
- ❌
RANK() OVER ()→ ✅ ORDER BY is required. - ❌ Forgetting DESC →
ORDER BY runsranks the lowest score as 1. - ❌ ROW_NUMBER over a non-unique ORDER BY → ✅ add a tie-breaker column for stable results.
Interview tip
Q: What is the difference between RANK and DENSE_RANK? A: Both give tied rows the same rank. RANK then skips numbers (1, 2, 2, 4) because it counts the tied rows; DENSE_RANK continues with the next number (1, 2, 2, 3). ROW_NUMBER never ties (1, 2, 3, 4).
Key points to remember
- Ranking functions number rows by the ORDER BY in OVER (required); brackets are empty and frames are not allowed.
- ROW_NUMBER: unique numbers, ties get different numbers (73 → 4 and 5).
- RANK: ties share a rank, then a gap (73, 73 → 4, 4; 59 → 6).
- DENSE_RANK: ties share a rank, no gap (73, 73 → 4, 4; 59 → 5).
- Choose: no ties needed → ROW_NUMBER; ties with gaps → RANK; ties without gaps → DENSE_RANK.
- Add a unique tie-breaker (e.g. id) to ROW_NUMBER's ORDER BY for deterministic results.
- Top-N per group: ROW_NUMBER() OVER (PARTITION BY group ORDER BY value DESC) in a subquery, then WHERE rn <= N.
- SQL Server sorts NULL as the lowest value: last in DESC, first in ASC.
Try it yourself
ROW_NUMBER vs RANK vs DENSE_RANK
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Rank inside each player (PARTITION BY)
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Top-N: best innings per player
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Practice: each player's top 2 innings
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Revision Flashcards
Tap a card to flip it and see the answer.
