Skip to main content

MySQL - RANK vs DENSE_RANK vs ROW_NUMBER - Different Tie Handling Approaches

In MySQL, you can utilize three Window Functions RANK(), DENSE_RANK(), and ROW_NUMBER() to add row numbers.

Each one of these ranking functions has a different way of handling ties.

We will prepare some sample data that will clearly illustrate how RANK(), DENSE_RANK(), and ROW_NUMBER() behave differently when it comes to ties in the data set you're analyzing.

With a clear understanding of their similarities and differences, you can choose the right ranking function to use to solve your real world problems.

Example Data​


The following table lists students along with their gender and score.

CREATE TABLE StudentScores
(`gender` varchar(1), `student` varchar(8), `score` int)
;

INSERT INTO StudentScores
(`gender`, `student`, `score`)
VALUES
('M', 'Tobi', 92),
('M', 'Tom', 92),
('M', 'Sam', 89),
('M', 'Ron', 100),
('F', 'Mary', 97),
('F', 'Emily', 95),
('F', 'Jennifer', 89),
('F', 'Lori', 89),
('F', 'Audra', 84);

Use

dbfiddle to execute SQL scripts on sample data.

Rank Rows with ROW_NUMBER()​

ROW_NUMBER() function always generates a unique ranking number even with ties.

Tobi and Tom's score tie at 92 so they are uniquely ranked as 4 and 5.

Sam, Jennifer, and Lori's score tie at 89 so they are uniquely ranked as 6, 7, and 8.

-- ROW_NUMBER breaks the tie
SELECT *
,ROW_NUMBER() OVER (
ORDER BY score DESC
) AS RowNum
FROM StudentScores
genderstudentscoreRowNum
MRon1001
FMary972
FEmily953
MTobi924
MTom925
MSam896
FJennifer897
FLori898
FAudra849

Rank Rows with RANK()​

RANK() function keeps the tie and skips numbers.

Tobi and Tom's score tie at 92 so they are both ranked as 4. Rank 5 is skipped.

Sam, Jennifer, and Lori's score tie at 89 so they are all ranked as 6. Rank 7 and 8 are skipped.

-- RANK() keeps the tie and skip numbers
SELECT *
,RANK() OVER (
ORDER BY score DESC
) AS RankNum
FROM StudentScores
genderstudentscoreRankNum
MRon1001
FMary972
FEmily953
MTobi924
MTom924
MSam896
FJennifer896
FLori896
FAudra849

Rank Rows with DENSE_RANK()​

DENSE_RANK() also keeps the tie, but does NOT skip numbers.

Tobi and Tom's score tie at 92 so they are both ranked as 4. Rank 5 is NOT skipped.

Sam, Jennifer, and Lori's score tie at 89 so they are all ranked as 5. Rank 6 and 7 are NOT skipped.

-- DENSE_RANK() keeps the tie as well, but does NOT skip numbers
SELECT *
,DENSE_RANK() OVER (
ORDER BY score DESC
) AS DenseRankNum
FROM StudentScores
genderstudentscoreDenseRankNum
MRon1001
FMary972
FEmily953
MTobi924
MTom924
MSam895
FJennifer895
FLori895
FAudra846

ROW_NUMBER() vs RANK() vs DENSE_RANK() Comparison​

Let's output ranking results in the same table to review how they handle ties differently.

SELECT *
,ROW_NUMBER() OVER (
ORDER BY score DESC
) AS RowNum
,RANK() OVER (
ORDER BY score DESC
) AS RankNum
,DENSE_RANK() OVER (
ORDER BY score DESC
) AS DenseRankNum
FROM StudentScores
genderstudentscoreRowNumRankNumDenseRankNum
MRon100111
FMary97222
FEmily95333
MTobi92444
MTom92544
MSam89665
FJennifer89765
FLori89865
FAudra84996

PARTITION BY Examples of ROW_NUMBER(), RANK(), DENSE_RANK()​

Notice that we exclude the optional PARTITION BY clause in above examples so the window we applied ranking functions to is the entire table.

Let's partition by gender into 2 windows and then have 3 ranking functions applied into each window.

SELECT *
,ROW_NUMBER() OVER (
PARTITION BY gender ORDER BY score DESC
) AS RowNum
,RANK() OVER (
PARTITION BY gender ORDER BY score DESC
) AS RankNum
,DENSE_RANK() OVER (
PARTITION BY gender ORDER BY score DESC
) AS DenseRankNum
FROM StudentScores

Review ranking results of gender F window and gender M window. You should observe the same behavior.

ROW_NUMBER() always breaks the tie and generates a unique number for each row within the window partion it belongs to.

RANK() keeps the tie and skips numbers. It leaves gaps.

DENSE_RANK() keeps the tie and does NOT skip numbers. It leaves no gaps.

genderstudentscoreRowNumRankNumDenseRankNum
FMary97111
FEmily95222
FJennifer89333
FLori89433
FAudra84554
MRon100111
MTobi92222
MTom92322
MSam89443

Conclusion​

Both RANK() and DENSE_RANK() will assign the same ranking to rows that have ties based by the ORDER BY clause.

DENSE_RANK() will always generate a contiguous sequence of ranks like (1,2,3,...) as it does NOT skip numbers.

RANK() will leave gaps after two or more rows with ties as it skips numbers.

ROW_NUMBER() function will always generate a unique ranking number even with ties.

Having a clear understanding on how RANK(), DENSE_RANK(), and ROW_NUMBER() handle ties differently is very important so they can be applied to solve and implement analytic SQL queries.