Hello! Welcome to our next lesson on advanced analysis with window functions.
In our last lesson, we focused on PARTITION BY, understanding how it creates logical windows to perform calculations on specific groups of data without collapsing the rows. We concluded by previewing that the real power of sequencing and ranking comes from adding an ORDER BY clause inside the OVER() clause.
Today, we will do exactly that. You will learn to apply ROW_NUMBER(), RANK(), and DENSE_RANK() to assign ranks to rows within a partition. These functions are workhorses in SQL analytics and are fundamental to solving a very common class of problems often called "Top-N" analysis (e.g., "find the top 5 salespeople in each region"), which frequently appear in technical interviews and real-world job tasks.
The Foundation of Ranking: ORDER BY within the OVER() Clause
While aggregate window functions like SUM() or AVG() can work on an unordered partition, ranking functions are meaningless without a defined order. You can't determine who is "1st", "2nd", or "3rd" without first sorting the data. Therefore, for all ranking functions, the ORDER BY sub-clause within OVER() is mandatory.
Let's watch a brief introduction that explains this core requirement.
SQL Ranking Window Functions | ROW_NUMBER, RANK, DENSE_RANK, NTILE | #SQL Course 24
The 'Data with Baraa' channel provides a great conceptual overview of ranking functions and their syntax rules. Pay close attention to the role of the ORDER BY clause.
Watch from the beginning to 05:12. Focus on the two key syntax rules mentioned: ranking functions don't take arguments inside their parentheses, and they always require an ORDER BY clause inside OVER().
The basic syntax will look like this:
SELECT
column_a,
column_b,
RANKING_FUNCTION() OVER (PARTITION BY ... ORDER BY ...) AS rank_column
FROM your_table;
With this foundation, let's explore the three main ranking functions. They all serve a similar purpose but have crucial differences in how they handle ties (rows with the same value in the ORDER BY column).
The Three Core Ranking Functions
The best way to understand these functions is to see them in action and compare their behavior directly.
SQL Window Function | How to write SQL Query using RANK, DENSE RANK, LEAD/LAG | SQL Queries Tutorial
This video from 'techTFQ' provides a concise, practical demonstration of ROW_NUMBER(), RANK(), and DENSE_RANK() side-by-side. It will clearly illustrate their differences.
Please watch the following segments carefully: ROW_NUMBER() (06:18 - 08:35): Notice how it assigns a unique number to every row, regardless of ties. RANK() (11:56 - 15:34): See how it handles ties by assigning the same rank and then skipping subsequent ranks. DENSE_RANK() (15:34 - 17:11): Contrast this with RANK(). Note that it also assigns the same rank to ties but does not skip ranks. Final Comparison (17:11 - 18:01): This segment puts all three next to each other, making the differences crystal clear.
Let's summarize the behavior you just saw.
1. ROW_NUMBER()
This function assigns a unique, sequential integer to every row within its partition, starting from 1.
- Tie Handling: It does not handle ties in any special way. If two rows have the same value in the
ORDER BYcolumn,ROW_NUMBER()will still assign them different, sequential numbers. From a programming perspective, think of it as simply assigning a line number after sorting. - Important Note: The order assigned to tied rows is not guaranteed to be consistent across different query executions unless you add a second, unique column to the
ORDER BYclause to act as a tie-breaker (e.g.,ORDER BY salary DESC, employee_id ASC).
2. RANK()
This function assigns a rank to each row based on the ORDER BY value.
- Tie Handling: Rows with the same value receive the same rank. However, the function then leaves a "gap" in the sequence. If two employees tie for 2nd place, they both get rank 2, and the next employee gets rank 4 (rank 3 is skipped).
- Analogy: This is like medal rankings in the Olympics. If two athletes tie for a gold medal (rank 1), no silver medal (rank 2) is awarded; the next athlete receives a bronze (rank 3).
3. DENSE_RANK()
This function also assigns a rank based on the ORDER BY value, handling ties identically to RANK().
- Tie Handling: Rows with the same value receive the same rank. The crucial difference is that
DENSE_RANK()does not leave a gap in the sequence. If two employees tie for 2nd place, they both get rank 2, and the next employee gets rank 3. - Use Case: This is ideal for creating "Top 10" leaderboards where you want exactly 10 unique ranks, even if there are ties.
Side-by-Side Comparison
To solidify your understanding, let's review a static comparison table.
SQL RANK vs DENSE_RANK vs ROW_NUMBER
This article from practicewindowfunctions.com provides a very clear summary table and example. It's a great reference to have.
First, review the summary table under the heading 'What Are SQL RANK, DENSE_RANK, and ROW_NUMBER...'. Then, look at the example under 'SQL RANK, DENSE_RANK, and ROW_NUMBER Examples and Comparison Results' to see a query and its output, which clearly illustrates the behavior.
Here is a visual summary of the behavior you've just reviewed:
| Function | Handles Ties? | Gap in Sequence After Tie? | Behavior |
|---|---|---|---|
ROW_NUMBER() |
❌ | N/A | Assigns a unique number to every row. |
RANK() |
✅ | ✅ | Skips rank(s) after a tie. |
DENSE_RANK() |
✅ | ❌ | Does not skip ranks after a tie. |
Test your understanding!
Given the following scores table, what would the RowNum, Rank, and DenseRank columns look like?
Table: scores
| player_name | score |
|---|---|
| Alice | 100 |
| Bob | 90 |
| Charlie | 90 |
| David | 85 |
Query:
SELECT
player_name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS RowNum,
RANK() OVER (ORDER BY score DESC) AS Rank,
DENSE_RANK() OVER (ORDER BY score DESC) AS DenseRank
FROM scores;
Try to predict the entire output table before looking at the answer.
Show answer
| player_name | score | RowNum | Rank | DenseRank |
|---|---|---|---|---|
| Alice | 100 | 1 | 1 | 1 |
| Bob | 90 | 2 | 2 | 2 |
| Charlie | 90 | 3 | 2 | 2 |
| David | 85 | 4 | 4 | 3 |
- RowNum: Assigns unique numbers 1, 2, 3, 4.
- Rank: Alice is 1. Bob and Charlie tie for 2. David is 4 because rank 3 was skipped.
- DenseRank: Alice is 1. Bob and Charlie tie for 2. David is 3 because no rank was skipped.
High-ROI Use Cases: From Theory to Practice
Understanding the mechanics is one thing; applying them to solve real problems is what makes these functions valuable for your career. Given your interest in practical, high-ROI skills, let's look at two extremely common scenarios where ranking functions are the go-to solution.
SQL Ranking Window Functions | ROW_NUMBER, RANK, DENSE_RANK, NTILE | #SQL Course 24
Let's return to the 'Data with Baraa' video, which demonstrates two powerful, job-relevant use cases for ROW_NUMBER().
This is a longer segment, but it's packed with value. Please watch from 20:05 to 35:26. Top-N Analysis (21:18 - 24:00): This section solves the classic "find the highest sale for each product" problem. Note the use of PARTITION BY to create the groups and a subquery (or CTE) to filter on the rank. Deduplication (29:56 - 35:26): This is a critical data cleaning task. The video shows how to use ROW_NUMBER() partitioned by a primary key and ordered by a timestamp to identify and isolate duplicate records, keeping only the most recent one. This is a brilliant and highly practical technique.
The technique shown in the video, using a Common Table Expression (CTE) or a subquery to first assign a rank and then filter on that rank, is the standard pattern for solving these problems.
For example, to find the top 3 highest-paid employees in each department:
WITH RankedEmployees AS (
SELECT
employee_name,
department,
salary,
-- DENSE_RANK is often best for "Top N" to avoid skipping ranks
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as salary_rank
FROM employees
)
SELECT
employee_name,
department,
salary,
salary_rank
FROM RankedEmployees
WHERE salary_rank <= 3;
This two-step process (rank, then filter) is necessary because you cannot use a window function directly in a WHERE clause.
Conclusion
Today we've added a powerful set of tools to your SQL arsenal. Ranking functions are essential for any kind of analysis that involves ordering and positioning, from simple leaderboards to complex data quality checks.
Key Takeaways:
- Ranking functions (
ROW_NUMBER,RANK,DENSE_RANK) require anORDER BYclause within theOVER()clause to define the sorting criteria. ROW_NUMBER()assigns a unique sequential number to every row, ignoring ties.RANK()assigns the same rank to tied rows and leaves a gap in the sequence.DENSE_RANK()assigns the same rank to tied rows but does not leave a gap, making it ideal for "Top-N" lists.- A common and powerful pattern is to use a CTE or subquery to assign ranks and then filter on the rank number in the outer query.
Preview of the Next Lesson:
We've now seen how to calculate aggregates over a window (SUM, AVG) and how to determine a row's position within a window (RANK). What if we want to access data from a different row within the same window? In our next lesson, we will explore LAG() and LEAD(), two powerful window functions that allow you to access data from preceding or succeeding rows, enabling you to calculate things like year-over-year growth or the time difference between consecutive events.