How to Use ROW_NUMBER and DENSE_RANK in MySQL
This article explains how to write MySQL queries using the
ROW_NUMBER() and DENSE_RANK() window
functions. You will learn the fundamental syntax of these functions,
understand how they differ when ranking data, and see practical query
examples that you can apply directly to your database.
Understanding Window Functions in MySQL
Window functions perform calculations across a set of table rows that are related to the current row. Unlike regular aggregate functions, window functions do not group multiple rows into a single output row; instead, they retain the identity of each individual row in the result set.
To use window functions like ROW_NUMBER() or
DENSE_RANK(), you must use the OVER clause,
which defines the “window” of rows the function operates on. The
OVER clause typically contains two components: *
PARTITION BY: Divides the result set into
partitions (groups) to apply the function. If omitted, the function
treats the entire result set as a single partition. *
ORDER BY: Specifies the logical order of
the rows within each partition.
How to Use ROW_NUMBER()
The ROW_NUMBER() function assigns a unique, sequential
integer to each row within a partition, starting at 1 for the first row
of each partition. If two rows have identical values in the ordered
column, ROW_NUMBER() will still assign them different,
sequential numbers.
Syntax
ROW_NUMBER() OVER (
[PARTITION BY partition_expression, ...]
ORDER BY sort_expression [ASC | DESC], ...
)Example
Suppose you have an employees table and want to assign a
unique sequential number to each employee within their respective
department, ordered by their salary from highest to lowest.
SELECT
employee_id,
department_id,
salary,
ROW_NUMBER() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS row_num
FROM employees;How to Use DENSE_RANK()
The DENSE_RANK() function assigns a rank to each row
within a partition based on the order defined in the OVER
clause. Identical values receive the same rank. Unlike the standard
RANK() function, DENSE_RANK() does not skip
any ranks when there are duplicate values, meaning the ranking sequence
is always consecutive (e.g., 1, 2, 2, 3).
Syntax
DENSE_RANK() OVER (
[PARTITION BY partition_expression, ...]
ORDER BY sort_expression [ASC | DESC], ...
)Example
Using the same employees table, if you want to rank
employees by their salary within each department but ensure that
employees with the same salary share the same rank without leaving gaps
in the ranking order, you use DENSE_RANK().
SELECT
employee_id,
department_id,
salary,
DENSE_RANK() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS dense_rnk
FROM employees;The Difference Between ROW_NUMBER() and DENSE_RANK()
To illustrate the practical difference between these two functions, consider a dataset containing three employees in the same department with the same salary:
| Employee ID | Salary | ROW_NUMBER() Result | DENSE_RANK() Result |
|---|---|---|---|
| 101 | $5,000 | 1 | 1 |
| 102 | $5,000 | 2 | 1 |
| 103 | $4,500 | 3 | 2 |
ROW_NUMBER()assigns sequential numbers (1, 2, 3) regardless of the duplicate $5,000 salaries.DENSE_RANK()assigns the same rank (1) to the duplicate $5,000 salaries, and the next lowest salary ($4,500) gets the immediate next rank (2).