Skip to content
Laviyx Learn

Try

SQL course menu

SQL ROW_NUMBER(): Number Rows, Top-N Per Group and Remove Duplicates

Learn SQL ROW_NUMBER() with examples: number rows in order, restart numbering per group with PARTITION BY, get the top row per group, find the latest record, and remove duplicate rows.

Advanced7 min readUpdated
On this page

ROW_NUMBER() gives each row a sequential number (1, 2, 3, …) in the order you choose. It sounds simple, but combined with PARTITION BY it solves some of the most common real-world problems: the top item per group, the latest record per customer, and removing duplicates.

Syntax#

SQL
ROW_NUMBER() OVER (
  [PARTITION BY column]
  ORDER BY column
)

ORDER BY decides the numbering order. PARTITION BY (optional) restarts the numbering for each group.

Number all rows#

SQLRun
SELECT name, salary,
       ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employees;
Output
name  | salary | rn
------+--------+---
Meera | 81000  | 1
Ravi  | 78000  | 2
Priya | 61000  | 3
Asha  | 52000  | 4
John  | 46000  | 5

Restart numbering per group#

SQLRun
SELECT name, department, salary,
       ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
ORDER BY department, rn;
Output
name  | department  | salary | rn
------+-------------+--------+---
Meera | Engineering | 81000  | 1
Ravi  | Engineering | 78000  | 2
John  | Marketing   | 46000  | 1
Priya | Sales       | 61000  | 1
Asha  | Sales       | 52000  | 2

Row 1 in each department is its highest earner.

Top row per group#

Window functions cannot be used in WHERE, so compute the number in a subquery (or CTE) and filter outside it. The highest-paid employee in each department:

SQLRun
SELECT name, department, salary
FROM (
  SELECT name, department, salary,
         ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
) t
WHERE rn = 1
ORDER BY department;
Output
name  | department  | salary
------+-------------+-------
Meera | Engineering | 81000
John  | Marketing   | 46000
Priya | Sales       | 61000

Change rn = 1 to rn <= 3 for the top three per group. This "top-N per group" pattern is one of the most frequently asked SQL interview questions.

Latest record per group#

Each customer's most recent order:

SQLRun
SELECT order_id, customer_id, order_date, amount
FROM (
  SELECT o.*,
         ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn
  FROM orders o
) t
WHERE rn = 1
ORDER BY customer_id;
Output
order_id | customer_id | order_date | amount
---------+-------------+------------+-------
102      | 1           | 2026-02-11 | 120
103      | 2           | 2026-02-20 | 560
104      | 5           | 2026-03-02 | 90

Removing duplicates#

Imagine a signups table where some people signed up twice:

SQL
SELECT email, signed_up,
       ROW_NUMBER() OVER (PARTITION BY email ORDER BY signed_up) AS rn
FROM signups
ORDER BY email, rn;
Output
email   | signed_up  | rn
--------+------------+---
a@x.com | 2026-01-01 | 1
a@x.com | 2026-01-05 | 2
b@x.com | 2026-01-02 | 1
b@x.com | 2026-01-09 | 2
c@x.com | 2026-01-03 | 1

Every row with rn > 1 is a duplicate of an earlier signup. Keep rn = 1 to see each person once, or delete the others. (The exact DELETE syntax for this varies; in PostgreSQL you can delete by ctid, and in SQL Server you can delete directly from a CTE.)

ROW_NUMBER and ties#

ROW_NUMBER always gives different numbers, even when values are tied. Which tied row gets 1 and which gets 2 is arbitrary unless you add a tie-breaker:

SQL
ROW_NUMBER() OVER (ORDER BY score DESC, student)

If tied rows should share a position, use RANK or DENSE_RANK.

Common mistakes#

Practice#

Practice exercise

From employees, return the second-highest earner in each department, if the department has one.

Show hint

Number rows per department by salary descending and keep rn = 2. Departments with only one employee will simply not appear.

Show solution
SQLRun
SELECT name, department, salary
FROM (
  SELECT name, department, salary,
         ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
) t
WHERE rn = 2;
Output
name | department  | salary
-----+-------------+-------
Ravi | Engineering | 78000
Asha | Sales       | 52000

Marketing has only John, so it has no second row.

Key takeaway#

Finished this lesson?