SQL Reference

SQL RANK() Function

You're going to learn RANK once and then spend your whole career choosing between it and ROW_NUMBER and DENSE_RANK. That's the real skill. The syntax takes ten minutes. Deciding which one to reach for takes a few hundred reps, and nobody walks you through the decision until you've already bombed one interview on it.

We're going to walk it together. Start with one rule: RANK leaves gaps after ties, DENSE_RANK doesn't, and ROW_NUMBER pretends ties don't exist. That single sentence will carry you through 90% of the interview questions you'll see. The other 10% comes down to understanding why the gap matters at all.

11%

Use RANK/DENSE_RANK

15%

Use ROW_NUMBER

3

Ranking functions to learn

1

Rule about tie gaps

Source: DataDriven analysis of 1,042 verified data engineering interview rounds.

RANK() Syntax

RANK is a window function. It requires an OVER clause with ORDER BY. PARTITION BY is optional. Without PARTITION BY, the entire result set is treated as one group.

RANK() OVER (
  [PARTITION BY partition_column]
  ORDER BY sort_column [ASC | DESC]
)

PARTITION BY (optional)

Divides the result set into groups. RANK restarts at 1 for each group. Without PARTITION BY, all rows belong to a single group. You can partition by one or more columns. PARTITION BY department creates one ranking per department. PARTITION BY department, fiscal_year creates one ranking per department-year combination.

ORDER BY (required)

Defines the ranking criteria. ORDER BY salary DESC ranks the highest salary as 1. ORDER BY created_at ASC ranks the oldest record as 1. You must include ORDER BY; without it, most engines throw an error. SQL Server allows omitting it but returns nondeterministic results, which is never what you want.

-- Basic RANK: global ranking by score
SELECT
  student_name,
  exam_score,
  RANK() OVER (ORDER BY exam_score DESC) AS score_rank
FROM exam_results;

-- RANK with PARTITION BY: ranking per department
SELECT
  department,
  employee_name,
  salary,
  RANK() OVER (
    PARTITION BY department
    ORDER BY salary DESC
  ) AS dept_salary_rank
FROM employees;

How RANK Handles Ties

If you remember nothing else from this page, remember that ties are the entire point of RANK. You'll see ORDER BY salary DESC in almost every interview example, and the moment two rows tie, RANK assigns the same number and jumps ahead. That gap tells you how many rows shared the spot. Five students at score 90 all get rank 2, and the next student gets rank 7. The formula is simple: next rank equals current rank plus number of tied rows.

Tie behavior with RANK()

NameScoreRANK()
Alice951
Bob902
Carol902
Dave854
Eve805

Key insight: Bob and Carol both score 90 and both get rank 2. Dave gets rank 4, not 3. Rank 3 does not exist because positions 2 and 3 were both consumed by the tie. This is the gap that interviewers ask about.

SELECT
  student_name,
  score,
  RANK() OVER (ORDER BY score DESC) AS rnk
FROM exam_results;

-- Alice  95  1
-- Bob    90  2
-- Carol  90  2
-- Dave   85  4   <-- rank 3 is skipped
-- Eve    80  5

RANK with PARTITION BY

PARTITION BY creates independent ranking windows. Each partition starts its ranking at 1. This is how you answer "rank employees within each department" or "top N per group" questions. Without PARTITION BY, every row competes against every other row globally. With it, rows only compete against rows in the same partition.

RANK() PARTITION BY department ORDER BY salary DESC

DepartmentNameSalaryRANK()
EngineeringAlice$150,0001
EngineeringBob$140,0002
EngineeringCarol$140,0002
EngineeringDave$130,0004
MarketingEve$120,0001
MarketingFrank$115,0002
MarketingGrace$110,0003

Engineering and Marketing each have their own ranking sequence. Bob and Carol tie at rank 2 within Engineering, so Dave gets rank 4 within Engineering. Marketing starts fresh at rank 1 for Eve. The partitions are completely independent.

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

Multiple PARTITION BY Columns

You can partition by more than one column. PARTITION BY department, fiscal_year creates a separate ranking for each department-year combination. If you have 5 departments and 3 years, you get 15 independent ranking sequences. Each combination restarts at 1.

SELECT
  department,
  fiscal_year,
  employee_name,
  sales_total,
  RANK() OVER (
    PARTITION BY department, fiscal_year
    ORDER BY sales_total DESC
  ) AS yearly_sales_rank
FROM annual_sales;

When to Use RANK()

RANK is the right choice when ties should share a position and the gap after ties communicates meaningful information. If you need consecutive numbers, use DENSE_RANK. If you need exactly one row per group, use ROW_NUMBER.

Competition-Style Leaderboards

When two contestants tie for 2nd place, there should be no 3rd place. The next contestant gets 4th. This is exactly what RANK does. The gap communicates that positions were consumed by the tie. Sports leaderboards, sales contests, and any ranking where ties share a position and the gap matters use RANK.

SELECT
  salesperson_id,
  region,
  total_revenue,
  RANK() OVER (
    PARTITION BY region
    ORDER BY total_revenue DESC
  ) AS revenue_rank
FROM quarterly_sales
WHERE fiscal_quarter = '2026-Q1';

Percentile and Threshold Analysis

RANK is useful when you want to know the exact position of a row relative to all others, including the effect of ties. If 10 people tie for 1st, the 11th person is rank 11, not rank 2. That accurately reflects the competitive position. Use this when you need to answer questions like 'how many people scored higher than this row?'

SELECT
  student_id,
  exam_score,
  RANK() OVER (ORDER BY exam_score DESC) AS score_rank,
  COUNT(*) OVER () AS total_students
FROM exam_results;
-- If score_rank = 15 and total_students = 200,
-- exactly 14 students scored higher

Filtering with Awareness of Tie Counts

When you write WHERE rank <= 3, RANK might return more than 3 rows if ties exist at the boundary. This is a feature, not a bug. If two people tie for 3rd, both are included. This behavior is what interviewers test. They want to see that you understand the result set size is not guaranteed when using RANK with a filter.

WITH ranked AS (
  SELECT
    product_id,
    category,
    units_sold,
    RANK() OVER (
      PARTITION BY category
      ORDER BY units_sold DESC
    ) AS sales_rank
  FROM product_sales
)
SELECT * FROM ranked WHERE sales_rank <= 3;
-- May return 4+ rows per category if ties exist at rank 3

RANK vs DENSE_RANK vs ROW_NUMBER

All three are ranking window functions. They only differ in how they handle ties. Picking the wrong one is a silent bug: the query runs, returns results, and the interviewer waits to see if you notice.

FunctionTies?Gaps?Best For
RANK()YesYesCompetition rankings, positional analysis
DENSE_RANK()YesNoNth distinct value, consecutive numbering
ROW_NUMBER()NoNoDeduplication, exactly one row per group

Interview tip: If the question says "top 3 employees," use ROW_NUMBER (exactly 3 rows). If it says "top 3 salaries," use DENSE_RANK (all employees at the top 3 salary levels). If it says "rank with ties," use RANK.

3 RANK Interview Questions

These questions test whether you understand RANK beyond the textbook definition. Each includes what the interviewer is actually evaluating and how to structure your answer.

Q1: Explain the difference between RANK() and DENSE_RANK(). Give an example where they return different results.

What they test:

Conceptual clarity on tie handling. The interviewer wants you to describe the gap behavior. RANK skips numbers after ties; DENSE_RANK does not. With scores 95, 90, 90, 85: RANK gives 1, 2, 2, 4. DENSE_RANK gives 1, 2, 2, 3. The difference matters when filtering: WHERE rank <= 3 with RANK excludes the score of 85 (rank 4), while WHERE dense_rank <= 3 includes it (dense_rank 3).

Approach:

Draw a concrete example with tied values. Show both outputs side by side. Then state when each is appropriate: RANK for positional ranking where gaps matter, DENSE_RANK when you need the Nth distinct value.

Q2: Write a query to find the top 2 highest-paid employees per department, including ties.

What they test:

This is where RANK shines vs ROW_NUMBER. ROW_NUMBER would return exactly 2 rows per department, breaking ties arbitrarily. RANK returns all employees tied at rank 1 or 2. The interviewer specifically said 'including ties' to signal that RANK (or DENSE_RANK) is the right choice.

Approach:

Use RANK() OVER (PARTITION BY department ORDER BY salary DESC) in a CTE, then filter WHERE rnk <= 2. Mention that the result may contain more than 2 rows per department if ties exist, and that this is the intended behavior.

Q3: A dashboard query uses RANK() but the stakeholder complains about 'missing' rank numbers. What is happening and how do you fix it?

What they test:

Real-world debugging of RANK behavior. The gaps after ties (1, 2, 2, 4) look like missing data to business users who expect consecutive numbers. The fix depends on the requirement: switch to DENSE_RANK if consecutive numbers are needed, or add a tooltip explaining the gap convention.

Approach:

Explain that RANK intentionally skips numbers after ties. If the business needs consecutive numbers, use DENSE_RANK. If the business needs exactly one row per rank, use ROW_NUMBER. Clarify the requirement before changing the query.

RANK and NULL Values

NULLs in the ORDER BY column affect rank assignments. The position of NULLs depends on the engine. PostgreSQL places NULLs last for ASC and first for DESC by default. SQL Server places NULLs first for ASC. Multiple NULL rows are treated as ties and receive the same rank.

-- Control NULL placement explicitly
SELECT
  employee_name,
  salary,
  RANK() OVER (
    ORDER BY salary DESC NULLS LAST
  ) AS salary_rank
FROM employees;

-- NULLs are ranked together at the bottom
-- All NULL salary rows share the same rank

Best practice: Always use NULLS LAST or NULLS FIRST explicitly when your ORDER BY column might contain NULLs. In engines that do not support this syntax (like older MySQL versions), use COALESCE to replace NULLs with a sentinel value.

SQL RANK Function FAQ

What does the RANK function do in SQL?+
The RANK function assigns a positional number to each row based on the ORDER BY clause within the OVER window. Rows with identical ORDER BY values receive the same rank. After a tie, the next rank skips ahead by the number of tied rows. Two rows tied at rank 2 means the next row gets rank 4, not 3. The gap indicates that two positions were consumed by the tie. RANK works in PostgreSQL, MySQL 8+, SQL Server, Snowflake, BigQuery, Databricks, and every modern SQL engine.
How does RANK handle ties?+
When two or more rows have identical values in the ORDER BY column, RANK assigns them all the same number. It then skips the subsequent numbers. If three rows tie at rank 5, the next row gets rank 8 (not 6). The formula is: next rank = current rank + number of tied rows. This behavior mirrors competition ranking: if three runners tie for 5th, there is no 6th or 7th place. The next finisher is 8th.
Can you use RANK without PARTITION BY?+
Yes. Without PARTITION BY, RANK operates over the entire result set as a single partition. Every row is ranked against every other row. PARTITION BY is optional and only needed when you want independent rankings per group. RANK() OVER (ORDER BY salary DESC) ranks all employees globally. RANK() OVER (PARTITION BY department ORDER BY salary DESC) restarts the ranking at 1 for each department.

A dozen reps and you'll stop second-guessing

The tie-gap rule sticks after ten or twelve practice problems. We'll walk you through the easy ones first and save the tricky ones for when you're ready.