✏️ Explanatory Question

NTILE and percentiles — divide customers into quartiles/deciles for segmentation

👁 9 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

102

NTILE and percentiles — divide customers into quartiles/deciles for segmentation

Level: Hard — Core to customer segmentation, RFM analysis, and "top 10%" business questions.

Scenario: Marketing wants to split all customers into 4 equal spending tiers (quartiles) — "Platinum, Gold, Silver, Bronze" — to target campaigns. Later they ask for the top 10% of spenders. How do you bucket rows into equal groups and compute percentile rank in SQL?

Sample Data — customers

nametotal_spent
Rumman5000
Krushna4000
Swetha3000
Ritesh2000
Manjula1500
Elitam1000
Sai800
Lakshmi500

What NTILE Does

NTILE(n) distributes the rows into n approximately equal buckets, assigning each row a bucket number from 1 to n. If rows don't divide evenly, the earlier buckets get the extra rows.

Key point: NTILE is count-based, not value-based. It splits by number of rows, so two customers with identical spend could land in different buckets. For value-based cutoffs, use PERCENT_RANK or CUME_DIST instead.

Solution — Quartile Segmentation with NTILE(4)

SELECT
    name,
    total_spent,
    NTILE(4) OVER (ORDER BY total_spent DESC) AS quartile,
    CASE NTILE(4) OVER (ORDER BY total_spent DESC)
        WHEN 1 THEN 'Platinum'
        WHEN 2 THEN 'Gold'
        WHEN 3 THEN 'Silver'
        WHEN 4 THEN 'Bronze'
    END AS tier
FROM customers;
nametotal_spentquartiletier
Rumman50001Platinum
Krushna40001Platinum
Swetha30002Gold
Ritesh20002Gold
Manjula15003Silver
Elitam10003Silver
Sai8004Bronze
Lakshmi5004Bronze

8 rows ÷ 4 buckets = 2 per tier. Evenly distributed.

Solution — "Top 10%" with PERCENT_RANK / CUME_DIST

-- PERCENT_RANK: relative standing from 0 (highest) as we order DESC
-- CUME_DIST: cumulative distribution (fraction of rows <= current)
SELECT
    name,
    total_spent,
    ROUND(PERCENT_RANK() OVER (ORDER BY total_spent DESC), 3) AS pct_rank,
    ROUND(CUME_DIST()    OVER (ORDER BY total_spent DESC), 3) AS cume_dist
FROM customers;

-- Get the TOP 10% of spenders (highest CUME_DIST fraction from the top)
SELECT name, total_spent
FROM (
    SELECT name, total_spent,
           CUME_DIST() OVER (ORDER BY total_spent DESC) AS cd
    FROM customers
) t
WHERE cd <= 0.10;   -- top 10%

NTILE vs PERCENT_RANK vs CUME_DIST

FunctionSplits ByBest For
NTILE(n)Equal row countsFixed buckets (quartiles, deciles)
PERCENT_RANK()Relative rank (0–1)"Better than X% of others"
CUME_DIST()Cumulative fraction"Top/bottom X%" cutoffs
The NTILE tie trap: Because NTILE splits by count, if customers #4 and #5 have the same spend but land on a bucket boundary, they'll be split into different tiers — which can feel "unfair." When ties must stay together, prefer value-based CUME_DIST/PERCENT_RANK cutoffs.

Interviewer follow-up: "Segment customers into quartiles WITHIN each region, not globally." → Add PARTITION BY region: NTILE(4) OVER (PARTITION BY region ORDER BY total_spent DESC). Each region gets its own independent 4 tiers — the partition restarts the bucketing per group.