SQL RANK() vs DENSE_RANK(): Differences & Examples
RANK() and DENSE_RANK() are window functions that give rows a position in an ordered list, and rows with equal values share the same position. After a tie, RANK() skips numbers (1, 2, 2, 4) while DENSE_RANK() does not (1, 2, 2, 3).
RANK() vs DENSE_RANK() Side by Side
Several managers in the Playground have the same number of direct reports, so ranking managers by team size produces ties. The query below ranks managers by team size with all three ranking functions:
WITH team AS (
SELECT manager_id, COUNT(*) AS reports
FROM employees
WHERE manager_id IS NOT NULL
GROUP BY manager_id
)
SELECT manager_id, reports,
ROW_NUMBER() OVER (ORDER BY reports DESC, manager_id) AS row_num,
RANK() OVER (ORDER BY reports DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY reports DESC) AS dense_rnk
FROM team
ORDER BY reports DESC, manager_id
LIMIT 8;
▶ Run this query in the SQL Playground
| manager_id | reports | row_num | rnk | dense_rnk |
|---|---|---|---|---|
| 9 | 14 | 1 | 1 | 1 |
| 2 | 13 | 2 | 2 | 2 |
| 7 | 13 | 3 | 2 | 2 |
| 10 | 13 | 4 | 2 | 2 |
| 8 | 12 | 5 | 5 | 3 |
| 1 | 11 | 6 | 6 | 4 |
| 3 | 11 | 7 | 6 | 4 |
| 4 | 11 | 8 | 6 | 4 |
Read down the three columns. Wherever two managers have the same number of reports, rnk and dense_rnk repeat a value while row_num keeps counting. After the tie, rnk jumps ahead to match the row position and dense_rnk simply continues with the next number.
| Function | Tied rows | After a tie | Values 90, 80, 80, 70 |
|---|---|---|---|
| ROW_NUMBER() | Different numbers | Keeps counting | 1, 2, 3, 4 |
| RANK() | Same number | Skips: the next rank is the row position | 1, 2, 2, 4 |
| DENSE_RANK() | Same number | No gap: the next rank is the next integer | 1, 2, 2, 3 |
Syntax
RANK() OVER ([PARTITION BY group_column] ORDER BY sort_column [DESC])
DENSE_RANK() OVER ([PARTITION BY group_column] ORDER BY sort_column [DESC])
Both functions take no arguments. The ORDER BY inside OVER is what defines the ranking: rows with the same ORDER BY values are ties. They are part of the window functions family and work in PostgreSQL, SQL Server, Oracle, MySQL 8.0+ and SQLite 3.25+.
Ranking Within Groups
Add PARTITION BY to rank inside each group. This ranks products by rating within their category:
SELECT category, name, rating,
RANK() OVER (PARTITION BY category ORDER BY rating DESC) AS rating_rank
FROM products
WHERE category IN ('Books', 'Toys')
ORDER BY category, rating_rank, name
LIMIT 10;
▶ Run this query in the SQL Playground
| category | name | rating | rating_rank |
|---|---|---|---|
| Books | The Manager's Path | 5 | 1 |
| Books | The Manager's Path | 4.9 | 2 |
| Books | The Pragmatic Programmer | 4.9 | 2 |
| Books | Zero to One (Thiel) | 4.9 | 2 |
| Books | System Design Interview Vol.2 | 4.7 | 5 |
| Books | The Manager's Path | 4.7 | 5 |
| Books | Deep Work (Newport) | 4.6 | 7 |
| Books | System Design Interview Vol.2 | 4.3 | 8 |
| Books | Deep Work (Newport) | 4 | 9 |
| Books | System Design Interview Vol.2 | 3.9 | 10 |
The rank starts again at 1 for each category, and products with the same rating share a rank.
Finding the Nth Highest Value
"Find the second (or third) highest salary" is a classic interview question. DENSE_RANK() answers it directly, because its ranks count distinct values:
WITH ranked AS (
SELECT first_name, last_name, salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT first_name, last_name, salary
FROM ranked
WHERE rnk = 3;
▶ Run this query in the SQL Playground
| first_name | last_name | salary |
|---|---|---|
| John | Lee | 203467 |
Use DENSE_RANK() here rather than RANK(). If two people shared the top salary, RANK() would number the rows 1, 1, 3 and "rank 2" would not exist at all.
Top N per Group, Keeping Ties
When everyone tied for a place should be included, filter on a rank instead of a row number. This returns each department's top two salary levels:
WITH ranked AS (
SELECT department, first_name, last_name, salary,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT department, first_name, last_name, salary, rnk
FROM ranked
WHERE rnk <= 2
ORDER BY department, rnk, last_name
LIMIT 8;
▶ Run this query in the SQL Playground
| department | first_name | last_name | salary | rnk |
|---|---|---|---|---|
| Customer Success | Nancy | Thompson | 203999 | 1 |
| Customer Success | Maria | Garcia | 191120 | 2 |
| Design | Amy | Nguyen | 194543 | 1 |
| Design | Joshua | Hall | 191657 | 2 |
| Engineering | Donna | Johnson | 199029 | 1 |
| Engineering | Dorothy | Martinez | 195690 | 2 |
| Finance | Paul | Robinson | 196032 | 1 |
| Finance | Linda | Martinez | 194604 | 2 |
Which One Should You Use?
| You want | Use |
|---|---|
| Exactly N rows per group, ties broken arbitrarily or by a tie-breaker | ROW_NUMBER() |
| Competition-style places, where two golds mean no silver | RANK() |
| The Nth distinct value, or the top N value levels | DENSE_RANK() |
Common Ranking Mistakes
Three errors that are easy to miss.
1. Using RANK() to find the Nth highest value
RANK() OVER (ORDER BY salary DESC) AS rnk
-- then WHERE rnk = 2
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
-- then WHERE rnk = 2
With a tie for first place, RANK() produces 1, 1, 3. Filtering for rank 2 then returns nothing.
2. Ranking in the wrong direction
RANK() OVER (ORDER BY salary)
RANK() OVER (ORDER BY salary DESC)
ORDER BY is ascending by default, so the smallest value gets rank 1. Add DESC when the best is the biggest.
3. Filtering on the rank in WHERE
SELECT name,
RANK() OVER (ORDER BY price DESC) AS rnk
FROM products
WHERE rnk <= 3;
WITH ranked AS (
SELECT name,
RANK() OVER (ORDER BY price DESC) AS rnk
FROM products
)
SELECT name, rnk
FROM ranked
WHERE rnk <= 3;
Window functions are calculated after WHERE. Compute the rank first, then filter in an outer query.
Practice RANK & DENSE_RANK Queries
4 exercises on the real Playground tables.
Write each query yourself first. Every solution has been run against the Playground database.
-
1Easy Rank products by review_count, highest first, with RANK(). Show the first eight rows. Table:
productsShow solution
SELECT name, review_count, RANK() OVER (ORDER BY review_count DESC) AS rnk FROM products ORDER BY rnk, name LIMIT 8;▶ Run this query in the SQL Playground
Products with the same review_count share a rank.
-
2Medium Find the employees with the second highest salary in the company. Table:
employeesShow solution
WITH ranked AS ( SELECT first_name, last_name, salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk FROM employees ) SELECT first_name, last_name, salary FROM ranked WHERE rnk = 2;▶ Run this query in the SQL Playground
DENSE_RANK() counts distinct salary values, so rank 2 is always the second highest value.
-
3Medium Rank shipping cities by their number of orders and show the top eight. Table:
ordersShow solution
SELECT shipping_city, COUNT(*) AS orders, RANK() OVER (ORDER BY COUNT(*) DESC) AS rnk FROM orders GROUP BY shipping_city ORDER BY rnk, shipping_city LIMIT 8;▶ Run this query in the SQL Playground
A window function can rank the result of GROUP BY: the aggregate is calculated first and then ranked.
-
4Challenging For each department, return the employees with the highest salary, including ties. Table:
employeesShow solution
WITH ranked AS ( SELECT department, first_name, last_name, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk FROM employees ) SELECT department, first_name, last_name, salary FROM ranked WHERE rnk = 1 ORDER BY department;▶ Run this query in the SQL Playground
Filtering on rank 1 keeps every employee who shares the top salary in a department: 10 rows.
📝 Quiz — RANK & DENSE_RANK
Test your understanding · 0/5 answered
Four rows have the scores 50, 40, 40, 30 (ranked highest first). What does RANK() return?
RANK() gives the tied rows 2 and then skips 3, so the last row is 4.
For the same scores, what does DENSE_RANK() return?
DENSE_RANK() leaves no gap after a tie: 1, 2, 2, 3.
Which function is the safest choice for "find the second highest salary"?
DENSE_RANK() numbers distinct values consecutively, so rank 2 always exists and is the second highest value.
What makes two rows a tie for RANK()?
Rows with equal ORDER BY values inside OVER receive the same rank.
What does PARTITION BY do in RANK() OVER (PARTITION BY category ORDER BY price DESC)?
Each category is ranked separately, starting again from 1.
RANK & DENSE_RANK Interview Questions
Q1. What is the difference between RANK() and DENSE_RANK()?
Both give tied rows the same rank. RANK() then skips as many numbers as there were extra tied rows, so ranks can have gaps. DENSE_RANK() continues with the next integer, so its ranks are consecutive.
Q2. How do you find the Nth highest salary?
Compute DENSE_RANK() OVER (ORDER BY salary DESC) in a CTE and select the rows where the rank equals N. It handles ties and works for any N.
Q3. When would you choose ROW_NUMBER() over RANK()?
When you need exactly one row per position, for example exactly three rows per group or one row to keep when removing duplicates. RANK() can return more rows than expected when there are ties.
Frequently Asked Questions
What is the difference between RANK and DENSE_RANK in SQL?
RANK() leaves gaps in the ranking after tied rows (1, 2, 2, 4). DENSE_RANK() does not leave gaps (1, 2, 2, 3). Both give tied rows the same rank.
Does RANK() need ORDER BY?
Yes. The ORDER BY inside the OVER clause defines what is being ranked. Without it every row is treated as tied and receives rank 1.
Can I use RANK() in a WHERE clause?
Not directly. Window functions are calculated after WHERE. Put the query in a CTE or subquery and filter on the rank column in the outer query.
What is the difference between RANK() and ROW_NUMBER()?
ROW_NUMBER() gives every row a different number, even when values are equal. RANK() gives equal values the same number.