Xorthax has learned how to rank rows, even when there are ties. But something still feels off.
“Why did the ranking skip a number?”
That’s where DENSE_RANK() comes in.
DENSE_RANK() assigns a rank to each row, just like RANK()—but without gaps.
DENSE_RANK() OVER (
PARTITION BY column
ORDER BY column
)
This works because:
PARTITION BY defines the groupORDER BY defines how rows are ranked| CustomerID | Amount |
|---|---|
| A | 200 |
| A | 150 |
| A | 150 |
| A | 100 |
SELECT CustomerID, Amount,
DENSE_RANK() OVER (
PARTITION BY CustomerID
ORDER BY Amount DESC
) AS DenseRankValue
FROM Orders;
Result:
| CustomerID | Amount | DenseRankValue |
|---|---|---|
| A | 200 | 1 |
| A | 150 | 2 |
| A | 150 | 2 |
| A | 100 | 3 |
Rows with the same value share the same rank, and the sequence continues without skipping numbers.
ORDER BY Amount DESC ranks rows from highest to lowest.
DENSE_RANK() assigns the same rank to tied rows.
Unlike RANK(), the next rank does not skip numbers.
That’s why the ranks go: 1, 2, 2, 3.
⚠️ Important: DENSE_RANK() still groups tied rows together, just like RANK().
The only difference is how the next rank is assigned—no gaps in the sequence.
For each product, assign a dense rank to orders based on quantity from lowest to highest. Only include orders between 500 and 600.
| Column | Type |
|---|---|
| orderdetailid | bigint |
| orderid | bigint |
| productid | bigint |
| quantity | bigint |
| unitprice | double precision |
| currencyid | bigint |
| Column |
|---|
| orderid |
| productid |
| dense_rank |
You’ve now seen three ways to assign positions to rows.
Next, you’ll learn how to combine these techniques with other queries.