ROW_NUMBER()

Assign unique sequential numbers — and solve top-N-per-group

Advanced 12 minROW_NUMBERrankingwindow

Unique Row Numbers

ROW_NUMBER() assigns a unique sequential integer to each row in its partition, starting at 1. It never produces ties — even rows with identical values get different numbers. Here's the key pattern:

Top-N per group — Number rows with PARTITION BY, then filter the results. WHERE rn = 1 keeps only the top item per group. WHERE rn <= 3 keeps the top three items per group for ranked displays.
Wrap in a CTE — The standard flow: a CTE assigns row numbers using ROW_NUMBER(), then the outer query filters by rn. This solves "most expensive product per category" and "latest order per customer" elegantly.
Deduplication — Number duplicate rows within each group and keep only rn = 1. This is a clean and efficient way to deduplicate data without using DELETE statements or creating temporary staging tables.
Arbitrary tiebreaker — When two rows have identical ORDER BY values, which one gets rn=1 is unpredictable. Add more columns to your ORDER BY clause to ensure deterministic, reproducible numbering.
Knowledge Canvas

How ROW_NUMBER Works

Assign unique sequential numbers

  • Assigns 1, 2, 3... to rows within each partition
  • No ties — every row gets a unique number
  • Tie-breaking is non-deterministic without sufficient ORDER BY
  • Most common use: top-N-per-group pattern

Top-N Per Group

The killer pattern for ROW_NUMBER

  • Assign ROW_NUMBER within each group (PARTITION BY)
  • Filter to rn = 1 for "latest" or "best" per group
  • Wrap in CTE or subquery for the WHERE filter
WITH ranked AS (
  SELECT *, ROW_NUMBER() OVER (
    PARTITION BY category
    ORDER BY price DESC
  ) AS rn
  FROM products
)
SELECT * FROM ranked WHERE rn = 1;

ROW_NUMBER Pitfalls

Where it goes wrong

  • Tied rows get arbitrary numbering without tiebreaker
  • Add a unique column to ORDER BY for deterministic results
  • Can't filter ROW_NUMBER in same query — use CTE
  • PARTITION BY is optional — omit for global numbering
Syntax
Syntax Template
1ROW_NUMBER() OVER (
2 PARTITION BY group_col
3 ORDER BY sort_col DESC
4) AS rn
ROW_NUMBER()Always unique — 1, 2, 3... (no ties)
PARTITION BYRestart numbering for each group
ORDER BYDetermines which row gets number 1
Sample Data
products
10 rows
namecategoryprice
Wireless MouseElectronics24.99
Office DeskFurniture189
LED Desk LampFurniture45.5
USB-C CableElectronics12.99
Bluetooth SpeakerElectronics79
Office ChairFurniture95
Coffee MakerAppliances89.99
Notebook SetStationery8.5
Wireless HeadphonesElectronics159
Standing DeskFurniture425
orders
12 rows
idcustomer_idorder_datetotal
5001C10012024-01-1559.98
5002C10022024-02-08449
5003C10012024-02-2012.5
5004C10032024-03-0589.99
5005C10062024-03-15245
5006C10052024-03-22159
5007C10042024-04-0245.5
5008C10092024-04-1279
5009C10122024-04-22425
5010C10072024-05-0518.99
5011C10082024-05-18134
5012C10102024-06-0865.5

Worked Example

Find the most expensive product in each category.

SQL
1WITH ranked AS (
2 SELECT
3 name,
4 category,
5 price,
6 ROW_NUMBER() OVER (
7 PARTITION BY category
8 ORDER BY price DESC
9 ) AS rn
10 FROM products
11)
12SELECT
13 category,
14 name,
15 price
16FROM ranked
17WHERE rn = 1
18ORDER BY
19 price DESC;
ROW_NUMBER assigns 1 to the most expensive product within each category. The outer WHERE rn = 1 keeps only the top product per category. This is the classic "top 1 per group" pattern.
Output
3 rows
categorynameprice
FurnitureStanding Desk425
ElectronicsWireless Headphones159
AppliancesCoffee Maker89.99

Key Concepts

1ROW_NUMBER never ties — even identical values get unique numbers
2Top-N per group: CTE with ROW_NUMBER, then WHERE rn <= N
3Add more ORDER BY columns for deterministic tiebreaking
4Also works for deduplication: keep WHERE rn = 1

Pro Tip

ROW_NUMBER enables top-N-per-group queries — one of the most asked SQL interview questions and one of the most useful analytical patterns.

When to Use

Most expensive product per category, latest order per customer, deduplication, pagination, sampling N rows per group.

Challenge

Solve the problem below

For each customer, find their 2 most recent orders. Show customer_id, order_date, total, and the row number.

Your Query