SQLab Hub
Lesson 40 · ROW_NUMBER()
Lesson 40 · More Topics

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.

# Sequential numbering ▶ Runnable Playground examples 🧪 4 practice exercises 📝 5-question quiz ⏱ ~18 min

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:

SQLite · runs in the Playground
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

Employees numbered from the highest salary down
first_namelast_namesalaryrow_num
StevenNguyen2044491
NancyThompson2039992
JohnLee2034673
DonnaJohnson1990294
MatthewWilson1965305
✓ 5 rows · real output from the Playground database
SQL · syntax
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:

SQLite · runs in the Playground
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

Row numbers restarting for each department
departmentfirst_namelast_namesalaryrow_num
DesignAmyNguyen1945431
DesignJoshuaHall1916572
DesignSandraLewis1870893
DesignAshleyThomas1717554
DesignJasonMitchell1568655
DesignMariaWhite1444816
DesignMichaelWalker1184657
DesignAshleyWhite751208
DesignRobertWright536309
DesignPaulThomas4604410
ProductStevenAdams1882731
ProductMichaelYoung1662882
✓ 12 rows · real output from the Playground database

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:

SQLite · runs in the Playground
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

Two highest-paid employees per department
departmentfirst_namelast_namesalary
Customer SuccessNancyThompson203999
Customer SuccessMariaGarcia191120
DesignAmyNguyen194543
DesignJoshuaHall191657
EngineeringDonnaJohnson199029
EngineeringDorothyMartinez195690
FinancePaulRobinson196032
FinanceLindaMartinez194604
✓ 8 rows · real output from the Playground database

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:

SQLite · runs in the Playground
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

Most recent order for each customer
user_idorder_idorder_datetotal
11092022-09-264064.75
2132023-04-021618.29
4112024-11-151518.36
51272024-05-184924.23
6232023-01-193252.76
✓ 5 rows · real output from the Playground database

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.

FunctionRows with equal values getExample for salaries 90, 80, 80, 70
ROW_NUMBER()Different numbers1, 2, 3, 4
RANK()The same number, then a gap1, 2, 2, 4
DENSE_RANK()The same number, no gap1, 2, 2, 3

Common ROW_NUMBER() Mistakes

Three errors and how to fix them.

1. Filtering on the row number in WHERE

❌ Error: window function in WHERE
SQL
SELECT first_name, salary,
       ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employees
WHERE rn <= 3;
✅ Number first, filter after
SQL
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

❌ Numbers in no particular order
SQL
ROW_NUMBER() OVER (PARTITION BY department)
✅ Explicit order
SQL
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

❌ One tied row is cut off
SQL
ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
-- then WHERE rn <= 3
✅ Ties kept together
SQL
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.

  1. 1Easy Number all products from the most expensive to the cheapest and show the first five. Table: products

    Show solution
    SQLite · runs in the Playground
    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; id breaks ties.

  2. 2Medium Return the most expensive product in each category. Table: products

    Show solution
    SQLite · runs in the Playground
    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.

  3. 3Medium Show each customer's first order (the earliest order date). Return five rows. Table: orders

    Show solution
    SQLite · runs in the Playground
    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.

  4. 4Challenging Return the third to fifth highest-paid employees in the whole company (a simple form of pagination). Table: employees

    Show solution
    SQLite · runs in the Playground
    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

Question 1 of 5

What does ROW_NUMBER() return?

Question 2 of 5

What does PARTITION BY do in ROW_NUMBER() OVER (PARTITION BY city ORDER BY id)?

Question 3 of 5

Why can you not write WHERE ROW_NUMBER() OVER (...) = 1?

Question 4 of 5

Two rows have the same value in the ORDER BY column. What does ROW_NUMBER() do?

Question 5 of 5

Which pattern returns the latest order for each customer?

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.