AIInterviewTraining logoAIInterview/Training
SQL & Data Engineering / 05
medium★ EssentialMetaSnowflakeDatabricks

Find the top-N records per group and a running total per group in SQL.

Top-N-per-group is the window-function question every data round asks, and the trap is RANK vs ROW_NUMBER vs DENSE_RANK. What interviewers want is picking the right ranking function for ties and understanding window frames. Here is the pattern and the tie nuance.

Updated Sep 2026 · Grounded in real GenAI, LLM, and AI/ML engineering interview loops and written to a senior-engineer editorial bar.

TL;DR: Apply a ranking window function partitioned by the group and ordered by the metric, then filter to rank <= N. Choose the function by how ties should behave: ROW_NUMBER returns exactly N (arbitrary tiebreak), RANK holds ties and skips numbers, DENSE_RANK holds ties without skipping. For a running total, SUM over an ordered frame (ROWS UNBOUNDED PRECEDING). Filtering a window result needs a subquery or CTE, because a window function cannot sit in WHERE.

SQL WINDOW FUNCTIONS (hover a row to see its frame)
RANK() OVER (PARTITION BY dept ORDER BY salary DESC)
EngDi$130k1
EngEli$110k2
EngFey$90k3
SalesAna$95k1
SalesBen$80k2
SalesCy$80k2
A window function computes across a set of rows without collapsing them. RANK leaves gaps after ties (the two 80s tie, then the next is 4), restarting in each partition.

How to approach it. Call this a window-function problem, and flag that the pivotal decision is which ranking function to use given the tie semantics. Point out the structural rule: window functions cannot appear in WHERE, so you rank inside a CTE/subquery and filter outside it.

A strong answer. Top-N per group:

WITH ranked AS (
  SELECT
    category,
    product,
    sales,
    ROW_NUMBER() OVER (
      PARTITION BY category ORDER BY sales DESC
    ) AS rn          -- exactly one row per rank; arbitrary among ties
  FROM products
)
SELECT category, product, sales
FROM ranked
WHERE rn <= 3;        -- top 3 per category

The choice of ranking function is the real test. Given tied values at the boundary, the three functions diverge:

FunctionSequence on ties<= N returnsUse when
ROW_NUMBER1,2,3,4exactly Nyou need exactly N rows per group
RANK1,1,3,4N or more (ties spill)top N including ties (competition style)
DENSE_RANK1,1,2,3top N distinct values"top 3 price points," not top 3 rows

ROW_NUMBER breaks ties arbitrarily unless you add a deterministic tiebreaker to ORDER BY, so two runs can return different rows. RANK and DENSE_RANK keep tied rows together; the difference is whether they skip the next number after a tie.

Running total per group uses an ordered frame:

SELECT
  category, product, sale_date, sales,
  SUM(sales) OVER (
    PARTITION BY category ORDER BY sale_date
    ROWS UNBOUNDED PRECEDING        -- running total within category, by date
  ) AS running_total
FROM products;

ROWS UNBOUNDED PRECEDING (to current row) defines the cumulative frame; omit it and the default frame (RANGE UNBOUNDED PRECEDING) can behave differently with tied ORDER BY values, a subtle bug worth naming.

Key takeaways

  • Window functions are evaluated after WHERE, so rank in a CTE and filter the rank outside it; this is structural, not stylistic.
  • Pick the ranking function from the tie requirement: ROW_NUMBER for exactly N, RANK for ties-included, DENSE_RANK for distinct values.
  • A running total needs an explicit ROWS frame; the default RANGE frame lumps tied ORDER BY rows together and inflates the cumulative value.
  • Always add a deterministic tiebreaker to ORDER BY, otherwise ROW_NUMBER results are non-reproducible.

What interviewers probe next.

  • "ROW_NUMBER vs RANK vs DENSE_RANK on ties?" Exactly the distinction above; pick by whether you want exactly N, ties-included, or distinct-values.
  • "Why the subquery/CTE?" Window functions are computed after WHERE, so you cannot filter on rn in the same query level; rank in a CTE, filter outside.
  • "ROWS vs RANGE frame?" ROWS counts physical rows; RANGE groups peer rows with equal ORDER BY values, which changes running totals when dates tie.
  • "Do this efficiently at scale?" Ensure the partition/order columns are indexed (or the table is partitioned/clustered on them) so the engine avoids a full sort.

Common mistakes.

  • Using RANK when the requirement is exactly N (ties return extra rows) or ROW_NUMBER when ties should be kept.
  • Trying to filter a window function in WHERE instead of a CTE/subquery.
  • Forgetting the frame clause on a running total and getting RANGE behavior on tied keys.
  • No deterministic tiebreaker in ORDER BY, so ROW_NUMBER results vary run to run.
That answer was free, and so are 10 per topic without an account. A free account doubles that to 20, remembers what you have answered, and tracks which topics you are weakest in.no card · Google sign-in · nothing to cancel
HOW DID IT GO?
0
UP NEXT ON YOUR JOURNEY
DISCUSSION · 0

No comments yet — be the first to share your approach.