datarekha

Ranking functions

ROW_NUMBER, RANK, DENSE_RANK, NTILE — the four ranking window functions, and the subtle tie behaviour that bites.

6 min read Intermediate SQL Lesson 14 of 27

What you'll learn

  • Exactly how ROW_NUMBER, RANK, and DENSE_RANK differ on ties
  • PARTITION BY to rank within each group
  • NTILE for bucketing rows into quartiles or quintiles
  • Picking the right function for the job

Before you start

SQL has four ranking functions. They look interchangeable, and they are not — the differences appear only on ties, and getting them wrong makes a leaderboard claim twelve people share first place.

The cleanest way to see it is one table. Imagine four scores — 100, 90, 90, 80 — and watch what each function does with the tie:

FunctionOutputBehaviour on the tie
ROW_NUMBER1, 2, 3, 4always unique; the tie is broken arbitrarily
RANK1, 2, 2, 4ties share a rank, then the next rank skips
DENSE_RANK1, 2, 2, 3ties share a rank, the next rank does not skip
NTILE(2)1, 1, 2, 2splits the rows into N equal buckets

On real rows

Here are four players, two of whom tie at 90:

nameteamscore
AnaA100
BenA90
CaraB90
DanB80
SELECT name, score,
       ROW_NUMBER() OVER (ORDER BY score DESC) AS rn,
       RANK()       OVER (ORDER BY score DESC) AS rk,
       DENSE_RANK() OVER (ORDER BY score DESC) AS drk
FROM   players;
namescorernrkdrk
Ana100111
Ben90222
Cara90322
Dan80443

Read the tied pair: ROW_NUMBER arbitrarily makes one of them 2 and the other 3; RANK calls both 2 and then jumps Dan to 4; DENSE_RANK calls both 2 and keeps Dan at 3. Same data, three different answers — choose deliberately.

PARTITION BY — rank inside each group

The usual question is not “rank everything” but “rank within each group.” PARTITION BY restarts the numbering whenever the group changes, which is how you get top-N per group:

WITH ranked AS (
  SELECT name, team, score,
         ROW_NUMBER() OVER (PARTITION BY team ORDER BY score DESC) AS rn
  FROM   players
)
SELECT team, name, score FROM ranked WHERE rn = 1;
teamnamescore
AAna100
BCara90

The numbering resets per team, so rn = 1 is each team’s leader. (Note the CTE: a window function can’t go directly in WHERE, so you compute it first and filter in the outer query.)

NTILE — split into equal buckets

NTILE(n) divides the ordered rows into n roughly equal buckets — the engine of decile reports and customer segmentation. NTILE(2) OVER (ORDER BY score DESC) over our four players puts Ana and Ben in bucket 1 (the top half) and Cara and Dan in bucket 2. When the rows don’t divide evenly, the extra rows go to the earlier buckets.

Practice

Quick check

0/3
Q1Four scores 100, 90, 90, 80. What does RANK() produce?
Q2You want to bucket customers into 5 equal-sized spend groups for a campaign. Which function?
Q3You're deduplicating and want exactly one row to survive per group, even on ties. Which function?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions

Related lessons

Explore further

Skip to content