πŸŽ‰ 75% of content is free forever β€” Unlock Premium from $10/mo β†’
CW
Search courses…
πŸ’Ό Servicesℹ️ Aboutβœ‰οΈ ContactView Pricing Plansfrom $10

ROW_NUMBER, RANK, and DENSE_RANK

Window FunctionsRanking Functions🟒 Free Lesson

Advertisement

Window Functions

ROW_NUMBER, RANK, and DENSE_RANK

Three ranking functions with critical differences in how they handle ties.

  • ROW_NUMBER β€” Unique sequential integers, no ties
  • RANK β€” Ties share a rank, gaps follow tied groups
  • DENSE_RANK β€” Ties share a rank, no gaps in numbering Choosing the right function depends on whether gaps in ranking matter.

The Three Ranking Functions

Visual Comparison

SalaryROW_NUMBERRANKDENSE_RANK$90,000111$85,000222$85,000 (tie)322$75,000443← No gap
RowSalaryROW_NUMBERRANKDENSE_RANK
A90000111
B85000222
C85000322
D80000443
E75000554

Notice how ROW_NUMBER assigns unique numbers even to ties, RANK skips to 4 after the tie at 2, and DENSE_RANK stays sequential.

Core Syntax

-- All three ranking functions side by side
SELECT
    employee_name,
    department,
    salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num,
    RANK()       OVER (PARTITION BY department ORDER BY salary DESC) AS rank_val,
    DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rank_val
FROM employees;

ROW_NUMBER β€” Unique Ordering

-- Select the most recent order per customer
WITH ranked_orders AS (
    SELECT
        customer_id,
        order_id,
        order_date,
        order_amount,
        ROW_NUMBER() OVER (
            PARTITION BY customer_id
            ORDER BY order_date DESC
        ) AS rn
    FROM orders
)
SELECT customer_id, order_id, order_date, order_amount
FROM ranked_orders
WHERE rn = 1;
-- Deduplicate: keep only the latest record per student
WITH deduplicated AS (
    SELECT
        student_id,
        course_name,
        grade,
        submission_date,
        ROW_NUMBER() OVER (
            PARTITION BY student_id, course_name
            ORDER BY submission_date DESC
        ) AS rn
    FROM student_submissions
)
SELECT student_id, course_name, grade, submission_date
FROM deduplicated
WHERE rn = 1;

RANK β€” Gaps After Ties

-- Competition results with RANK (ties share rank, gaps follow)
SELECT
    contestant_name,
    score,
    RANK() OVER (ORDER BY score DESC) AS competition_rank
FROM contest_results
ORDER BY competition_rank;

-- Output:
-- Alice    | 98 | 1
-- Bob      | 95 | 2
-- Charlie  | 95 | 2
-- Diana    | 92 | 4   <-- Gap after the tie at rank 2

DENSE_RANK β€” No Gaps

-- Grade assignment with DENSE_RANK (no gaps after ties)
SELECT
    student_name,
    score,
    DENSE_RANK() OVER (ORDER BY score DESC) AS grade_rank,
    CASE
        WHEN DENSE_RANK() OVER (ORDER BY score DESC) <= 3 THEN 'Honors'
        WHEN DENSE_RANK() OVER (ORDER BY score DESC) <= 7 THEN 'Pass'
        ELSE 'Needs Improvement'
    END AS designation
FROM student_scores;

Practical Scenario: Top N Per Group

-- Top 3 earners in each department
WITH department_ranks AS (
    SELECT
        employee_name,
        department,
        salary,
        DENSE_RANK() OVER (
            PARTITION BY department
            ORDER BY salary DESC
        ) AS dept_rank
    FROM employees
)
SELECT employee_name, department, salary, dept_rank
FROM department_ranks
WHERE dept_rank <= 3
ORDER BY department, dept_rank;
-- Bottom 2 performers per department
WITH bottom_performers AS (
    SELECT
        employee_name,
        department,
        performance_score,
        ROW_NUMBER() OVER (
            PARTITION BY department
            ORDER BY performance_score ASC
        ) AS bottom_rank
    FROM employees
    WHERE performance_score IS NOT NULL
)
SELECT employee_name, department, performance_score
FROM bottom_performers
WHERE bottom_rank <= 2;

When to Use Each

ScenarioRecommendedWhy
PaginationROW_NUMBERNeed unique sequential IDs
DeduplicationROW_NUMBERPick exactly one row per group
Competition rankingRANKTies should be equal, gaps expected
Leaderboard displayDENSE_RANKClean numbering without gaps
Percentile assignmentNTILESplit into N equal groups

Combining with Other Window Functions

-- Full salary analysis with ranking and comparison
SELECT
    employee_name,
    department,
    salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num,
    RANK()       OVER (PARTITION BY department ORDER BY salary DESC) AS rank_val,
    DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rank_val,
    LAG(salary)  OVER (PARTITION BY department ORDER BY salary DESC) AS prev_salary,
    salary - LAG(salary) OVER (PARTITION BY department ORDER BY salary DESC) AS gap_to_prev
FROM employees
ORDER BY department, row_num;

Need Expert SQL Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement