🔥 0
0
Lesson 2 of 6 18 min +200 XP

Ranking Functions

Need to find the top 3 products in each category? Show a leaderboard? Remove duplicate records? Ranking functions are your answer.

The Four Ranking Functions

Function Handles Ties Gaps Use Case
ROW_NUMBER() Assigns different numbers No Deduplication, pagination
RANK() Same rank for ties Yes Sports rankings
DENSE_RANK() Same rank for ties No Medal standings
NTILE(n) N/A N/A Quartiles, percentiles

Visual Comparison

Consider these test scores: 95, 95, 90, 85, 85, 80

score ROW_NUMBER RANK DENSE_RANK NTILE(3)
95 1 1 1 1
95 2 1 1 1
90 3 3 2 2
85 4 4 3 2
85 5 4 3 3
80 6 6 4 3

Notice: ROW_NUMBER always has unique values, RANK skips numbers (1,1,3), DENSE_RANK doesn't (1,1,2).

ROW_NUMBER(): Unique Sequential Numbers

Basic Syntax

SELECT
  name,
  department,
  salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC) as rank
FROM employees;

Deduplication: Keep Most Recent

-- Find duplicate emails, keep only the most recent user
WITH ranked AS (
  SELECT *,
    ROW_NUMBER() OVER (
      PARTITION BY email
      ORDER BY created_at DESC
    ) as rn
  FROM users
)
SELECT * FROM ranked WHERE rn = 1;
Why ROW_NUMBER for Deduplication?

ROW_NUMBER guarantees exactly one row gets number 1 per partition. RANK or DENSE_RANK could give ties, returning multiple rows.

Pagination

-- Get page 3 (rows 21-30) of products
WITH numbered AS (
  SELECT *,
    ROW_NUMBER() OVER (ORDER BY name) as row_num
  FROM products
)
SELECT * FROM numbered
WHERE row_num BETWEEN 21 AND 30;

RANK(): Gaps After Ties

SELECT
  name,
  sales,
  RANK() OVER (ORDER BY sales DESC) as sales_rank
FROM salespeople;
name sales sales_rank
Alice 50000 1
Bob 45000 2
Carol 45000 2
Dave 40000 4

Notice: Bob and Carol tie at rank 2, Dave is rank 4 (not 3). Like Olympic rankings!

DENSE_RANK(): No Gaps

SELECT
  name,
  sales,
  DENSE_RANK() OVER (ORDER BY sales DESC) as sales_rank
FROM salespeople;
name sales sales_rank
Alice 50000 1
Bob 45000 2
Carol 45000 2
Dave 40000 3

Now Dave is rank 3. No gaps!

NTILE(n): Divide into Buckets

NTILE divides rows into n roughly equal groups.

SELECT
  name,
  salary,
  NTILE(4) OVER (ORDER BY salary DESC) as quartile
FROM employees;
name salary quartile interpretation
Alice 150000 1 Top 25%
Bob 120000 1 Top 25%
Carol 100000 2 25-50%
Dave 80000 2 25-50%
Eve 70000 3 50-75%
Frank 60000 3 50-75%
Grace 55000 4 Bottom 25%
Henry 50000 4 Bottom 25%

Use Case: Percentile Analysis

-- Find customers in top 10% by spending
SELECT *
FROM (
  SELECT
    customer_id,
    total_spent,
    NTILE(10) OVER (ORDER BY total_spent DESC) as decile
  FROM customer_spending
) ranked
WHERE decile = 1;

Real-World: Top N Per Group

-- Top 3 products per category by revenue
WITH ranked_products AS (
  SELECT
    category,
    product_name,
    revenue,
    ROW_NUMBER() OVER (
      PARTITION BY category
      ORDER BY revenue DESC
    ) as rank
  FROM products
)
SELECT category, product_name, revenue
FROM ranked_products
WHERE rank <= 3;
category product_name revenue
Electronics iPhone 15 2500000
Electronics MacBook Pro 1800000
Electronics AirPods Pro 950000
Clothing Winter Jacket 450000
Clothing Running Shoes 380000
Clothing Jeans 320000

Key Takeaways

  • ROW_NUMBER - Always unique, perfect for deduplication and pagination
  • RANK - Same rank for ties, gaps after (1,1,3)
  • DENSE_RANK - Same rank for ties, no gaps (1,1,2)
  • NTILE - Divides into equal buckets for percentiles

Next up: Aggregate Window Functions - Running totals, moving averages, and more.

🧠 Quick Quiz

Test your understanding of this lesson.

1

Three salespeople have sales of 100, 100, 80. What does DENSE_RANK() return for the person with 80?

2

You want to select only the most recent order for each customer. Which approach works?

3

NTILE(4) on 10 rows divides them into groups of what sizes?