SQL RANK() OVER (PARTITION BY) Explained with Examples

PARTITION BY is the primitive underneath every cohort dashboard, every leaderboard, every top-N-per-group report you have ever rendered. It appears in 21% of verified DE interview SQL rounds, and almost all of those pair it with a ranking function. The pattern is so common that most senior interview rubrics call it out as a pass/fail signal for the 'can you read a fact table' bar.

Syntax Breakdown

Every part of RANK() OVER (PARTITION BY ... ORDER BY ...) has a specific job. Understanding each component is the difference between copying a pattern and knowing when to reach for it.

21%
Use PARTITION BY
~all
Pair with ranking
11%
Use RANK directly
L5+
Expected at senior

Examples

The four examples below trace the same pipeline stage at increasing levels of sophistication: a basic rank per partition, a top-N filter sitting downstream of the rank, a deduplication pattern on a natural key, and the aggregate-then-rank shape that powers most cohort dashboards. Treat each one as a reusable component in your query architecture.

Prepare for the interview
01 / Open invite
02min.

Know Window Functions the way the interviewer who asks it knows it.

a Window Functions query, the same shape a screen would give you.
The diff against expected. Where ties broke. What you missed.
sandbox
1SELECT user_id,
2 COUNT(*) AS sessions
3FROM events
4WHERE ts >= NOW() - INTERVAL '7 day'
5
Execute your solution0.4s avg.
LyftInterview question
Solve a Window Functions problem
Prepare for the interview
03 / From the bank03 of many
03hand-picked.

Top Selling Items

Easy10 min

Revenue crowns the winners. Who sold the most?

Syntax Breakdown

RANK() OVER (
  PARTITION BY department
  ORDER BY salary DESC
) AS salary_rank

Syntax Parts

RANK()

The ranking function. It assigns a rank to each row within its partition based on the ORDER BY clause. If two rows have the same value, they get the same rank, and the next rank is skipped. For example: 1, 2, 2, 4 (rank 3 is skipped because two rows tied at rank 2).

OVER(...)

The window specification. This tells SQL how to define the 'window' of rows for the ranking. Without OVER, RANK() would not know what to rank or how to partition the data.

PARTITION BY column

Divides the result set into independent groups. RANK() restarts at 1 for each partition. Think of it as GROUP BY for window functions, except it does not collapse rows. Every row keeps its identity.

ORDER BY column

Determines the ranking order within each partition. ORDER BY salary DESC means the highest salary gets rank 1. Without ORDER BY, the rank would be meaningless because there is no ordering to rank by.

Basic: Rank employees by salary within each department

SELECT
  department,
  name,
  salary,
  RANK() OVER (
    PARTITION BY department
    ORDER BY salary DESC
  ) AS salary_rank
FROM employees;

-- department  | name    | salary  | salary_rank
-- ------------|---------|---------|------------
-- Engineering | Alice   | 180000  | 1
-- Engineering | Bob     | 165000  | 2
-- Engineering | Carol   | 165000  | 2
-- Engineering | Dave    | 150000  | 4
-- Marketing   | Eve     | 140000  | 1
-- Marketing   | Frank   | 130000  | 2

Bob and Carol both earn $165K, so they both get rank 2. Dave gets rank 4, not rank 3. The gap in ranks tells you that ties exist above.

Top-N per group: Find the top 3 earners in each department

WITH ranked AS (
  SELECT
    department,
    name,
    salary,
    RANK() OVER (
      PARTITION BY department
      ORDER BY salary DESC
    ) AS rn
  FROM employees
)
SELECT department, name, salary
FROM ranked
WHERE rn <= 3;

If there are ties at rank 3, RANK() returns all tied rows. That means you might get more than 3 rows per department. If you want exactly 3, use ROW_NUMBER() instead. Interviewers specifically test whether you understand this distinction.

Rank customers by total spend

WITH customer_spend AS (
  SELECT
    customer_id,
    region,
    SUM(amount) AS total_spend
  FROM orders
  GROUP BY customer_id, region
)
SELECT
  region,
  customer_id,
  total_spend,
  RANK() OVER (
    PARTITION BY region
    ORDER BY total_spend DESC
  ) AS spend_rank
FROM customer_spend;

The CTE computes total_spend with GROUP BY. The outer query applies RANK() to the aggregated result. This two-step pattern (aggregate, then rank) is extremely common.

Find the most recent order per customer

WITH latest AS (
  SELECT
    customer_id,
    order_id,
    order_date,
    amount,
    RANK() OVER (
      PARTITION BY customer_id
      ORDER BY order_date DESC
    ) AS rn
  FROM orders
)
SELECT customer_id, order_id, order_date, amount
FROM latest
WHERE rn = 1;

If a customer has two orders on the same date, RANK() returns both. Use ROW_NUMBER() if you want exactly one row per customer regardless of ties.

RANK vs ROW_NUMBER vs DENSE_RANK

The three ranking functions differ only in how they handle ties. Knowing which one to use is a common interview differentiator.

ROW_NUMBER()

Always assigns unique sequential numbers. No ties. 1, 2, 3, 4, 5. Best for: Deduplication, pagination, exactly-N-per-group queries. When you need exactly one row or exactly N rows, no exceptions. Example: 1, 2, 3, 4, 5 (even if rows 2 and 3 have the same value).

RANK()

Assigns the same rank to tied rows. Skips subsequent ranks. 1, 2, 2, 4. Best for: Competition-style ranking where ties should be visible and the gap communicates how many items tied. Sports rankings, leaderboards. Example: 1, 2, 2, 4 (rank 3 is skipped).

DENSE_RANK()

Assigns the same rank to tied rows. Does NOT skip subsequent ranks. 1, 2, 2, 3. Best for: When you need consecutive rank numbers without gaps. Useful for bucketing or when the rank number itself is used in downstream logic. Example: 1, 2, 2, 3 (no gaps).

3 RANK OVER PARTITION Interview Questions

Three questions that test RANK OVER PARTITION understanding. Each includes the hint and recommended approach.

Q1

For each department, find the employee with the second-highest salary. If there is a tie for the highest salary, the second-highest is the next distinct salary below the tie.

DENSE_RANK is the right choice here, not RANK. With RANK, if two employees tie for first place, rank 2 is skipped. With DENSE_RANK, the next distinct salary gets rank 2. Use DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dr, then filter WHERE dr = 2.

Q2

Write a query that returns the top 2 products by revenue in each category. If there are ties, include all tied products.

RANK() handles this naturally because it assigns the same rank to tied rows. Filter WHERE rank <= 2 and you get all products at rank 1 and rank 2, including ties. Use RANK() OVER (PARTITION BY category ORDER BY revenue DESC) AS rn in a CTE, then WHERE rn <= 2.

Q3

For each customer, flag their orders as 'first', 'second', or 'other' based on order date. If two orders share the same date, both should be flagged with the same label.

Use DENSE_RANK for consecutive numbering, then CASE WHEN to map ranks to labels. PARTITION BY customer, ORDER BY date. CASE WHEN DENSE_RANK() OVER (PARTITION BY customer_id ORDER BY order_date) = 1 THEN 'first' WHEN ... = 2 THEN 'second' ELSE 'other' END AS order_label.

RANK OVER PARTITION FAQ

Can I use RANK() without PARTITION BY?+
Yes. Without PARTITION BY, RANK() treats the entire result set as one partition. It ranks all rows against each other instead of ranking within groups. This is valid but less common in interviews. Most interview questions specifically test the PARTITION BY component because it requires understanding grouped ranking.
Why can I not use RANK() in a WHERE clause directly?+
Window functions are evaluated after WHERE, HAVING, and GROUP BY. The rank does not exist yet when the WHERE clause runs. You must compute the rank in a subquery or CTE first, then filter on it in the outer query. This is why the CTE pattern (WITH ranked AS ... SELECT ... WHERE rn <= N) is so common.
When should I use RANK() vs ROW_NUMBER() in an interview?+
Use ROW_NUMBER() when you need exactly N rows per group, with no exceptions for ties. Use RANK() when ties should be preserved and the rank gap should communicate the tie. If the interviewer says 'top 3' and two items tie for third place, ask whether they want exactly 3 rows or all items ranked 3 or above. That clarifying question itself demonstrates strong SQL understanding.
02 / Why practice

Build it once, reuse it everywhere

  1. 01

    Active recall beats re-reading by 50%

    Cognitive-science meta-reviews (Dunlosky et al., 2013) rank practice testing as a top-tier study technique, while re-reading and highlighting rank near the bottom

  2. 02

    76% of hiring managers reject on the coding task, not the resume

    From HackerRank's 2024 Developer Skills Report. Candidates who look strong on paper still fail the live screen if they haven't done timed, executable practice

  3. 03

    Five problem shapes cover 80% of data engineer loops

    Dedup, sessionization, top-N-per-group, slowly-changing dimensions, partition tricks. Writing the shapes by hand turns the unfamiliar into pattern recognition

Related Guides