Intermediate: combining and changing data · Window functions · topic 33 of 62 · 35 min
Ranking: ROW_NUMBER, RANK, DENSE_RANK and NTILE
Explanation
Why ranking functions?
Leaderboards, "top 3 sellers", "the 2nd highest salary" and "the latest order of each customer" all need a number that says where a row stands. You could sort and count by eye, but SQL can write the position on each row for you. That is what ranking functions do.
ORDER BY inside OVER
A ranking function needs to know what "first" means, so you put an ORDER BY inside the OVER brackets. This ORDER BY decides the numbering only. It does not sort the final result; for that you still need the normal ORDER BY at the end of the query. Ranking functions take no argument: you write ROW_NUMBER(), with empty brackets.
Three ways to handle ties
In COMPANY, Anita Desai and Vikram Singh both earn 95000. Watch what each function does with them.
SELECT name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC, name) AS row_num,
RANK() OVER (ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense
FROM employees
ORDER BY salary DESC, name;
-- Meera Iyer | 250000 | 1 | 1 | 1
-- Rahul Sharma | 120000 | 2 | 2 | 2
-- Pooja Gupta | 110000 | 3 | 3 | 3
-- Farhan Ali | 105000 | 4 | 4 | 4
-- Anita Desai | 95000 | 5 | 5 | 5
-- Vikram Singh | 95000 | 6 | 5 | 5
-- Lakshmi Pillai | 80000 | 7 | 7 | 6
-- ... 11 rows| Function | On a tie | After the tie | Use it for |
|---|---|---|---|
| ROW_NUMBER() | Different numbers (5, 6) | Continues: 7 | Exactly one row per position: paging, de-duplication, "latest row" |
| RANK() | Same number (5, 5) | Skips: 7 | Sports-style ranks: two joint 5th, nobody is 6th |
| DENSE_RANK() | Same number (5, 5) | No gap: 6 | "The 2nd highest salary" counted by distinct values |
Ranking inside groups
Add PARTITION BY and the numbering starts again at 1 in each partition. This answers "who is the best paid in each department?" in one pass.
SELECT name, dept_id, salary,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dept_rank
FROM employees
WHERE dept_id IS NOT NULL
ORDER BY dept_id, dept_rank, name;
-- Meera Iyer | 1 | 250000 | 1
-- Rahul Sharma | 1 | 120000 | 2
-- Anita Desai | 1 | 95000 | 3
-- Vikram Singh | 1 | 95000 | 3
-- Pooja Gupta | 2 | 110000 | 1
-- ...NTILE: buckets
NTILE(n) deals the ordered rows into n buckets numbered 1 to n, as evenly as possible, like dealing cards. NTILE(4) gives quartiles: bucket 1 is the top quarter. If the rows do not divide evenly, the first buckets get one extra row. Seven students in four buckets gives sizes 2, 2, 2 and 1.
SELECT name, marks, NTILE(4) OVER (ORDER BY marks DESC) AS quartile
FROM students
WHERE marks IS NOT NULL
ORDER BY marks DESC;
-- Priya Nair | 91 | 1
-- Divya Kumar | 88 | 1
-- Karan Mehta | 79 | 2
-- Asha Verma | 76 | 2
-- Ravi Kumar | 67 | 3
-- Imran Khan | 58 | 3
-- Arjun Reddy | 45 | 4NTILE looks only at row positions, not values: two equal marks can fall into different buckets. Also note that NULLs sort first in ascending order in SQLite, so decide on purpose what should happen to them, either filter them out or use NULLS LAST.
PERCENT_RANK and CUME_DIST in one line
PERCENT_RANK() gives (rank − 1) / (rows − 1), a number from 0 to 1 that says what fraction of rows rank below this one. CUME_DIST() gives the fraction of rows at or below this one. With ORDER BY salary, Lakshmi (80000) has a PERCENT_RANK of 0.4 among the 11 employees.
Common mistakes
- Using ROW_NUMBER when ties matter. For "everyone who shares 1st place", use RANK or DENSE_RANK.
- No tie-breaker in ROW_NUMBER, so results change between runs. Add a unique column.
- Expecting the ORDER BY inside OVER to sort the output. Add an ORDER BY at the end of the query too.
- Forgetting PARTITION BY and ranking the whole company when you wanted each department.
- Leaving NULLs in a ranking without thinking. Filter them or place them with NULLS FIRST / NULLS LAST.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Ranking: ROW_NUMBER, RANK, DENSE_RANK and NTILE · 1/7
Ranking functions write a position number on every row. Leaderboards, top three lists and second highest salary questions all use them.
Prefer a video by a teacher? Find free videos on “sql row_number rank dense_rank ntile difference” on YouTube ↗
In real life: A merit list for college admissions
Admission offices publish merit lists where students with equal marks share a rank, and the next rank is skipped, exactly what RANK() does. Many also break ties by a second rule, such as the marks in a key subject or the date of birth, which becomes a second column in the ORDER BY inside OVER. Dividing applicants into bands for counselling rounds is an NTILE-style job.
SELECT name, course, marks,
RANK() OVER (PARTITION BY course ORDER BY marks DESC) AS merit_rank
FROM students
WHERE marks IS NOT NULL
ORDER BY course, merit_rank;Practice (0/4 done)
Show the three ranking functions side by side. From employees, return name, salary, ROW_NUMBER() named row_num, RANK() named rnk and DENSE_RANK() named dense, all ordered by salary from highest to lowest. Break ties in ROW_NUMBER by name (A to Z). Sort the result by salary descending, then name.
Rank employees inside their own department by salary, highest first, using RANK(). Leave out the employee with no department. Return name, dept_id, salary and the rank named dept_rank, sorted by dept_id, then dept_rank, then name.
Split the students into 4 groups by marks with NTILE(4), the highest marks in group 1. The student with no marks (NULL) must be left out. Return name, marks and the group named quartile, sorted by marks from highest to lowest.
Make a merit list per course. Leave out the student with no marks. Return course, name, marks and DENSE_RANK() by marks (highest first) inside each course, named course_rank, sorted by course, then course_rank.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Running totals, moving averages and framesWindow functions · same module
- LAG, LEAD, FIRST_VALUE and LAST_VALUEWindow functions · same module
- Classic interview queries, part 1Interview questions and practice projects
- Subqueries, CTEs and window functionsBackend Development · SQL in depth
- Counting and grouping with hashingData Structures and Algorithms (DSA) for interviews · Hashing, two pointers and sliding window
- Percentiles, quartiles and outliersData Science, Machine Learning and AI · Statistics made simple
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Salaries in order are 300, 200, 200, 100. What does RANK() OVER (ORDER BY salary DESC) give?
Answer: 1, 2, 2, 4
RANK gives ties the same number and then skips, so the next rank is 4.
For the same salaries (300, 200, 200, 100), what does DENSE_RANK() give?
Answer: 1, 2, 2, 3
DENSE_RANK gives ties the same number and leaves no gaps.
Why add name to ROW_NUMBER() OVER (ORDER BY salary DESC, name)?
Answer: It makes the numbering of tied salaries the same on every run
Without a tie-breaker, tied rows can be numbered in any order.
Ten rows are split with NTILE(3). How many rows land in each bucket?
Answer: 4, 3, 3
NTILE spreads rows as evenly as possible, and the first buckets get the extra rows.
Does the ORDER BY inside OVER (...) sort the rows of the final result?
Answer: No; it only decides the numbering, so add an ORDER BY at the end for the output order
The output order is set only by the query's own ORDER BY.
Two employees tie for the 2nd highest salary. Which function returns both of them for "rank = 2"?Related
Answer: DENSE_RANK
DENSE_RANK (and RANK) give ties the same rank; ROW_NUMBER numbers them 2 and 3.
In a de-duplication with ROW_NUMBER() OVER (PARTITION BY email ORDER BY id), which rows are deleted to keep the lowest id?Related
Answer: rn > 1
Row 1 has the lowest id in each email and stays; the others are copies.
employees has 11 rows. How many rows does SELECT name, AVG(salary) OVER (PARTITION BY dept_id) FROM employees return?Related
Answer: 11
A window function never removes rows; each of the 11 rows gets its department average.
Two employees tie on salary. How do you make ORDER BY salary DESC LIMIT 5 give the same rows every time?Related
Answer: Add a tie-breaker, such as ORDER BY salary DESC, id
A second sort column decides between tied rows.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Window functions” before its exam: Running totals, moving averages and frames, LAG, LEAD, FIRST_VALUE and LAST_VALUE, Top-N per group and de-duplication.
- Module 7 exam: Window functions · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Changing data, transactions and ACID. 62 topics left in the course before the final exam.