SQL ROW_NUMBER(): Syntax, PARTITION BY & Top-N Examples
ROW_NUMBER() is a window function that gives each row a sequential number: 1, 2, 3 and so on, in the order you choose. With PARTITION BY the numbering starts again at 1 for every group, which is how "top N per group" and "latest row per customer" queries are written.
What Is ROW_NUMBER()?
ROW_NUMBER() adds a column of row numbers without removing or merging any rows. The ORDER BY inside OVER (...) decides which row is number 1. Here the highest salary gets 1:
SELECT first_name, last_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC, id) AS row_num
FROM employees
ORDER BY row_num
LIMIT 5;
▶ Run this query in the SQL Playground
| first_name | last_name | salary | row_num |
|---|---|---|---|
| Steven | Nguyen | 204449 | 1 |
| Nancy | Thompson | 203999 | 2 |
| John | Lee | 203467 | 3 |
| Donna | Johnson | 199029 | 4 |
| Matthew | Wilson | 196530 | 5 |
ROW_NUMBER() OVER (
[PARTITION BY group_column]
ORDER BY sort_column
)
ROW_NUMBER() is one of the SQL window functions. It is available in PostgreSQL, SQL Server, Oracle, MySQL 8.0 and later, and SQLite 3.25 and later.
Restarting the Count With PARTITION BY
PARTITION BY department splits the rows into one group per department and numbers each group separately. Two departments are shown so you can see the count restart:
SELECT department, first_name, last_name, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC, id) AS row_num
FROM employees
WHERE department IN ('Design', 'Product')
ORDER BY department, row_num
LIMIT 12;
▶ Run this query in the SQL Playground
| department | first_name | last_name | salary | row_num |
|---|---|---|---|---|
| Design | Amy | Nguyen | 194543 | 1 |
| Design | Joshua | Hall | 191657 | 2 |
| Design | Sandra | Lewis | 187089 | 3 |
| Design | Ashley | Thomas | 171755 | 4 |
| Design | Jason | Mitchell | 156865 | 5 |
| Design | Maria | White | 144481 | 6 |
| Design | Michael | Walker | 118465 | 7 |
| Design | Ashley | White | 75120 | 8 |
| Design | Robert | Wright | 53630 | 9 |
| Design | Paul | Thomas | 46044 | 10 |
| Product | Steven | Adams | 188273 | 1 |
| Product | Michael | Young | 166288 | 2 |
The first Design employee is 1, and so is the first Product employee. Unlike GROUP BY, the rows are not collapsed: every employee is still there.
Top N Rows per Group
A window function cannot be used in WHERE, because WHERE runs before the numbers are calculated. Compute the row number in a CTE first, then filter on it. This returns the two best-paid employees in every department:
WITH ranked AS (
SELECT department, first_name, last_name, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC, id) AS rn
FROM employees
)
SELECT department, first_name, last_name, salary
FROM ranked
WHERE rn <= 2
ORDER BY department, salary DESC
LIMIT 8;
▶ Run this query in the SQL Playground
| department | first_name | last_name | salary |
|---|---|---|---|
| Customer Success | Nancy | Thompson | 203999 |
| Customer Success | Maria | Garcia | 191120 |
| Design | Amy | Nguyen | 194543 |
| Design | Joshua | Hall | 191657 |
| Engineering | Donna | Johnson | 199029 |
| Engineering | Dorothy | Martinez | 195690 |
| Finance | Paul | Robinson | 196032 |
| Finance | Linda | Martinez | 194604 |
Change rn <= 2 to rn = 1 for only the top row of each group, or to any other number for a different N.
Keeping the Latest Row per Customer
The same pattern picks one row per group when a table holds several. Number each customer's orders from newest to oldest and keep number 1:
WITH numbered AS (
SELECT user_id, id AS order_id, order_date, total,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC, id DESC) AS rn
FROM orders
)
SELECT user_id, order_id, order_date, total
FROM numbered
WHERE rn = 1
ORDER BY user_id
LIMIT 5;
▶ Run this query in the SQL Playground
| user_id | order_id | order_date | total |
|---|---|---|---|
| 1 | 109 | 2022-09-26 | 4064.75 |
| 2 | 13 | 2023-04-02 | 1618.29 |
| 4 | 11 | 2024-11-15 | 1518.36 |
| 5 | 127 | 2024-05-18 | 4924.23 |
| 6 | 23 | 2023-01-19 | 3252.76 |
This is also the standard way to remove duplicates: partition by the columns that define a duplicate, order by whichever copy you want to keep, and keep rn = 1.
Ties and Why the Order Must Be Complete
ROW_NUMBER() never repeats a number. If two rows have the same value in the ORDER BY column, the database still has to put one first, and without further instructions it may choose differently each time. Add a unique column, such as id, as a tie-breaker so the result is the same on every run. All examples on this page do that.
| Function | Rows with equal values get | Example for salaries 90, 80, 80, 70 |
|---|---|---|
| ROW_NUMBER() | Different numbers | 1, 2, 3, 4 |
| RANK() | The same number, then a gap | 1, 2, 2, 4 |
| DENSE_RANK() | The same number, no gap | 1, 2, 2, 3 |
Common ROW_NUMBER() Mistakes
Three errors and how to fix them.
1. Filtering on the row number in WHERE
SELECT first_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employees
WHERE rn <= 3;
WITH numbered AS (
SELECT first_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employees
)
SELECT first_name, salary
FROM numbered
WHERE rn <= 3;
WHERE is evaluated before window functions, so the alias does not exist yet. Wrap the query in a CTE or subquery and filter in the outer query.
2. Leaving out ORDER BY inside OVER
ROW_NUMBER() OVER (PARTITION BY department)
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC, id
)
Without ORDER BY the numbering is arbitrary (and SQL Server rejects the query). State the order you mean.
3. Using ROW_NUMBER() when ties should share a place
ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
-- then WHERE rn <= 3
DENSE_RANK() OVER (ORDER BY score DESC) AS rnk
-- then WHERE rnk <= 3
If two rows tie for third place, ROW_NUMBER() keeps only one of them. Use RANK() or DENSE_RANK() when equal values should be treated equally.
Practice ROW_NUMBER() Queries
4 exercises on the real Playground tables.
Write each query yourself first. Every solution has been run against the Playground database.
-
1Easy Number all products from the most expensive to the cheapest and show the first five. Table:
productsShow solution
SELECT name, price, ROW_NUMBER() OVER (ORDER BY price DESC, id) AS row_num FROM products ORDER BY row_num LIMIT 5;▶ Run this query in the SQL Playground
ORDER BY price DESC inside OVER makes the most expensive product number 1;
idbreaks ties. -
2Medium Return the most expensive product in each category. Table:
productsShow solution
WITH ranked AS ( SELECT category, name, price, ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC, id) AS rn FROM products ) SELECT category, name, price FROM ranked WHERE rn = 1 ORDER BY category;▶ Run this query in the SQL Playground
One row per category, 10 in total: the row numbered 1 in each partition.
-
3Medium Show each customer's first order (the earliest order date). Return five rows. Table:
ordersShow solution
WITH numbered AS ( SELECT user_id, id AS order_id, order_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date, id) AS rn FROM orders ) SELECT user_id, order_id, order_date FROM numbered WHERE rn = 1 ORDER BY user_id LIMIT 5;▶ Run this query in the SQL Playground
Ordering by order_date ascending makes the earliest order number 1 for each customer.
-
4Challenging Return the third to fifth highest-paid employees in the whole company (a simple form of pagination). Table:
employeesShow solution
WITH numbered AS ( SELECT first_name, last_name, salary, ROW_NUMBER() OVER (ORDER BY salary DESC, id) AS rn FROM employees ) SELECT rn, first_name, last_name, salary FROM numbered WHERE rn BETWEEN 3 AND 5 ORDER BY rn;▶ Run this query in the SQL Playground
Filtering on a range of row numbers returns one "page" of 3 rows.
📝 Quiz — ROW_NUMBER()
Test your understanding · 0/5 answered
What does ROW_NUMBER() return?
ROW_NUMBER() numbers rows 1, 2, 3 and so on, following the ORDER BY inside OVER.
What does PARTITION BY do in ROW_NUMBER() OVER (PARTITION BY city ORDER BY id)?
PARTITION BY splits the rows into groups, and each group is numbered separately from 1.
Why can you not write WHERE ROW_NUMBER() OVER (...) = 1?
Window functions are evaluated after WHERE, so the number must be computed in a CTE or subquery and filtered outside it.
Two rows have the same value in the ORDER BY column. What does ROW_NUMBER() do?
ROW_NUMBER() always assigns distinct numbers. Add a tie-breaker column to make the order predictable.
Which pattern returns the latest order for each customer?
Number each customer's orders from newest to oldest and keep the row numbered 1.
ROW_NUMBER() Interview Questions
Q1. What is the difference between ROW_NUMBER(), RANK() and DENSE_RANK()?
All three number rows in a window order. ROW_NUMBER() gives every row a different number. RANK() gives tied rows the same number and leaves a gap after them. DENSE_RANK() gives tied rows the same number with no gap.
Q2. How do you select the top N rows per group?
Compute ROW_NUMBER() OVER (PARTITION BY group ORDER BY measure DESC) in a CTE or subquery, then filter the outer query with WHERE rn <= N.
Q3. How would you delete duplicate rows but keep one copy?
Number the rows with ROW_NUMBER(), partitioned by the columns that define a duplicate, and delete the rows numbered greater than 1.
Frequently Asked Questions
What is ROW_NUMBER() in SQL?
ROW_NUMBER() is a window function that assigns a unique sequential integer to each row, starting at 1, in the order given by the ORDER BY inside its OVER clause.
What is the difference between ROW_NUMBER() and RANK()?
ROW_NUMBER() always gives different numbers, even to rows with equal values. RANK() gives equal rows the same number and then skips ahead.
Does ROW_NUMBER() need ORDER BY?
In SQL Server, yes: ORDER BY inside OVER is required. Other databases allow it to be left out, but the numbering is then arbitrary, so you should always include it.
Does MySQL support ROW_NUMBER()?
Yes, from MySQL 8.0. Older versions have no window functions and need a user variable or a self-join instead.