What is ROW_NUMBER()?
ROW_NUMBER() is an analytic function in Oracle SQL that assigns a unique sequential number to each row within a result set or partition, based on a specific order.
Unlike the ROWNUM pseudocolumn (which is assigned before sorting), ROW_NUMBER() is assigned after the ORDER BY clause, which makes it more reliable for pagination, ranking, and ordered row selection.
Syntax
ROW_NUMBER() OVER (ORDER BY column_name)
Optional Partitioning:
ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY another_column)
Key Points
| Feature | Details |
|---|---|
| Type | Analytic function |
| Starts from | 1 for each partition |
| Can reset per group? | Yes, with PARTITION BY |
| Order matters? | Yes, you must specify ORDER BY |
| Deterministic? | Yes — unlike ROWNUM, it’s order-aware |
Example 1: Assign Row Numbers to All Employees by Salary
SELECT employee_id, first_name, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num FROM employees;
Result:
| employee_id | first_name | salary | row_num |
|---|---|---|---|
| 102 | Sarah | 15000 | 1 |
| 101 | John | 12000 | 2 |
| 103 | Linda | 10000 | 3 |
ROW_NUMBER()assigns unique numbers after sorting by salary DESC.
Example 2: Top Paid Employee in Each Department
SELECT *
FROM (
SELECT
department_id,
employee_id,
salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
FROM employees
)
WHERE rn = 1;
Explanation:
- The
PARTITION BYclause resets row number for each department. - The outer query filters to keep only the top-paid employee per department.
Pagination with ROW_NUMBER()
Example: Get records from 11 to 20
SELECT * FROM ( SELECT e.*, ROW_NUMBER() OVER (ORDER BY employee_id) AS rn FROM employees e ) WHERE rn BETWEEN 11 AND 20;
This is a common pattern for paginated queries:
- Inner query assigns row numbers based on the desired sort order.
- Outer query filters based on range.
Compare: ROW_NUMBER() vs RANK() vs DENSE_RANK()
| Function | Handles Ties? | Skips Numbers? | Use Case |
|---|---|---|---|
ROW_NUMBER() | No — all rows get unique numbers | Yes | Pagination |
RANK() | Yes | Yes | Ranking with gaps |
DENSE_RANK() | Yes | No | Ranking without gaps |
Example (Same Salary):
SELECT employee_id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_number, RANK() OVER (ORDER BY salary DESC) AS rank, DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank FROM employees;
Real-World Use Cases
| Use Case | How ROW_NUMBER() Helps |
|---|---|
| Pagination | Get rows between N and M efficiently |
| Top-N per group | Select top 1 product/customer/order in each group |
| De-duplication | Keep only the first row based on sorting |
| Ranking | Assign sequential ranks with order control |
Limitations
| Limitation | Explanation |
|---|---|
Must use subquery to filter ROW_NUMBER() | You can’t use it directly in WHERE |
Needs ORDER BY | Required for row numbering to work |
| Not compatible with older Oracle versions | Requires Oracle 8i or newer |
Summary
| Feature | Description |
|---|---|
| Name | ROW_NUMBER() |
| Type | Analytic function |
| Purpose | Assign sequential row number based on sort |
| Partitions supported? | Yes |
| Order-aware? | Yes |
| Use cases | Pagination, ranking, top-N, de-duplication |
Better than ROWNUM? | Yes — because it’s predictable and flexible |
Bonus: Oracle 12c+ Pagination (with FETCH)
If you’re on Oracle 12c or later, you can also do pagination like this:
SELECT * FROM employees ORDER BY employee_id OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
But ROW_NUMBER() remains useful when:
- You need to rank within partitions.
- You want fine control over row numbers.
