Bird
Raised Fist0
SQLquery~10 mins

Top-N per group query in SQL - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Top-N per group query
Start with full table
↓
Partition data by group
↓
Order each group by criteria
↓
Assign row numbers within each group
↓
Filter rows where row number <= N
↓
Return top N rows per group
This flow shows how to get the top N rows for each group by partitioning, ordering, numbering, and filtering.
Execution Sample
SQL
SELECT department, employee, salary
FROM (
  SELECT department, employee, salary,
         ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
) sub
WHERE rn <= 2;
This query returns the top 2 highest salaries per department from the employees table.
Execution Table
StepRowdepartmentemployeesalaryROW_NUMBER() rnActionOutput Included
11SalesAlice900001Assign rn=1 for highest salary in SalesYes
22SalesBob850002Assign rn=2 for second highest in SalesYes
33SalesCharlie700003Assign rn=3, excluded rn>2No
44HRDiana800001Assign rn=1 for highest salary in HRYes
55HREvan750002Assign rn=2 for second highest in HRYes
66HRFiona600003Assign rn=3, excluded rn>2No
77ITGeorge950001Assign rn=1 for highest salary in ITYes
88ITHannah900002Assign rn=2 for second highest in ITYes
99ITIan850003Assign rn=3, excluded rn>2No
10ExitAll rows processed, filtered to rn <= 2Query ends
💡 Rows with rn > 2 are excluded, so only top 2 salaries per department are returned.
Variable Tracker
VariableStartAfter Row 1After Row 2After Row 3After Row 4After Row 5After Row 6After Row 7After Row 8After Row 9Final
rn (row number per group)N/A1 (Sales)2 (Sales)3 (Sales)1 (HR)2 (HR)3 (HR)1 (IT)2 (IT)3 (IT)Final assigned per row
Key Moments - 2 Insights
Why do some rows have rn values greater than 2 but are not included in the output?
Rows with rn > 2 are filtered out by the WHERE clause (rn <= 2), so only the top 2 rows per group remain, as shown in execution_table rows 3, 6, and 9.
How does the ROW_NUMBER() function know to restart counting for each department?
Because of PARTITION BY department in the window function, ROW_NUMBER() resets to 1 for each new department group, as seen in execution_table steps 4 and 7.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table, what is the rn value for employee 'Evan' in the HR department?
A1
B3
C2
DNone
💡 Hint
Check execution_table row 5 under the 'ROW_NUMBER() rn' column.
At which step does the query exclude a row because rn > 2?
AStep 5
BStep 3
CStep 7
DStep 9
💡 Hint
Look for rows marked 'excluded rn>2' in the Action column in execution_table.
If we change rn <= 2 to rn <= 3 in the WHERE clause, how would the output change?
AMore rows per group would be included
BFewer rows per group would be included
CNo change in output
DQuery would error
💡 Hint
Refer to the exit_note and see which rows are currently excluded due to rn > 2.
Concept Snapshot
Top-N per group query syntax:
Use ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY criteria) to number rows per group.
Filter with WHERE rn <= N to get top N rows per group.
This returns the highest (or lowest) N rows per group based on ordering.
Commonly used for ranking or limiting results within groups.
Full Transcript
This visual execution trace shows how to write a SQL query to get the top N rows per group. The query uses ROW_NUMBER() with PARTITION BY to assign a row number to each row within its group, ordered by a criteria such as salary descending. Then it filters to keep only rows where the row number is less than or equal to N, effectively selecting the top N rows per group. The execution table walks through each row, showing the assigned row number and whether it is included in the output. The variable tracker shows how the row number resets for each group. Key moments clarify why some rows are excluded and how partitioning works. The quiz tests understanding of row numbers and filtering. The snapshot summarizes the syntax and behavior for quick reference.

Practice

(1/5)
1. What does the ROW_NUMBER() OVER (PARTITION BY column ORDER BY column) clause do in a SQL query?
easy
A. Assigns a unique rank number to each row within each group defined by the partition column.
B. Counts the total number of rows in the entire table.
C. Filters rows to only include those with the highest value in the column.
D. Joins two tables based on the partition column.

Solution

  1. Step 1: Understand ROW_NUMBER() function

    ROW_NUMBER() assigns a unique sequential number to rows in the result set.
  2. Step 2: Understand PARTITION BY usage

    PARTITION BY divides rows into groups, and ROW_NUMBER() restarts numbering for each group.
  3. Final Answer:

    Assigns a unique rank number to each row within each group defined by the partition column. -> Option A
  4. Quick Check:

    ROW_NUMBER() with PARTITION BY = unique rank per group [OK]
Hint: ROW_NUMBER() with PARTITION BY ranks rows per group [OK]
Common Mistakes:
  • Thinking ROW_NUMBER() counts total rows
  • Confusing PARTITION BY with filtering
  • Assuming it filters rows automatically
2. Which of the following SQL queries correctly selects the top 2 highest salaries per department from an employees table?
easy
A. SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn FROM employees) sub WHERE rn <= 2;
B. SELECT * FROM employees ORDER BY salary DESC LIMIT 2 PARTITION BY department;
C. SELECT * FROM employees WHERE ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) <= 2;
D. SELECT TOP 2 * FROM employees PARTITION BY department ORDER BY salary DESC;

Solution

  1. Step 1: Understand ROW_NUMBER() usage in subquery

    ROW_NUMBER() must be used in a subquery or CTE to assign ranks before filtering.
  2. Step 2: Filter rows by rank in outer query

    The outer query filters rows where the rank (rn) is 2 or less per department.
  3. Final Answer:

    SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn FROM employees) sub WHERE rn <= 2; -> Option A
  4. Quick Check:

    Use subquery with ROW_NUMBER() and filter by rank [OK]
Hint: Use subquery with ROW_NUMBER() then filter by rank [OK]
Common Mistakes:
  • Trying to filter ROW_NUMBER() directly in WHERE clause
  • Using LIMIT with PARTITION BY (not valid syntax)
  • Using TOP without proper partitioning
3. Given the table sales with columns region, salesperson, and amount, what will this query return?
SELECT region, salesperson, amount FROM (SELECT region, salesperson, amount, ROW_NUMBER() OVER (PARTITION BY region ORDER BY amount DESC) AS rn FROM sales) AS ranked WHERE rn = 1;
medium
A. All sales records sorted by amount descending.
B. All sales records where amount equals 1.
C. The highest sale amount record for each region.
D. The lowest sale amount record for each region.

Solution

  1. Step 1: Analyze ROW_NUMBER() with PARTITION BY region

    Rows are grouped by region and ranked by amount descending, so rn=1 is highest amount per region.
  2. Step 2: Filter WHERE rn = 1

    This keeps only the top-ranked row per region, i.e., the highest sale amount record.
  3. Final Answer:

    The highest sale amount record for each region. -> Option C
  4. Quick Check:

    ROW_NUMBER() = 1 per group returns top record [OK]
Hint: Filter WHERE rn=1 to get top record per group [OK]
Common Mistakes:
  • Thinking it returns all rows sorted
  • Confusing rn=1 with lowest amount
  • Assuming amount = 1 filters rows
4. Identify the error in this SQL query that tries to get the top 3 recent orders per customer from orders table:
SELECT customer_id, order_id, order_date, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rank FROM orders WHERE rank <= 3;
medium
A. Missing GROUP BY clause for customer_id.
B. ROW_NUMBER() cannot be used with PARTITION BY.
C. ORDER BY must be ascending, not descending.
D. You cannot filter on an alias in the WHERE clause directly.

Solution

  1. Step 1: Understand alias usage in WHERE clause

    Aliases like 'rank' defined in SELECT cannot be used in WHERE clause directly because WHERE is evaluated before SELECT.
  2. Step 2: Correct approach using subquery or CTE

    Use a subquery or CTE to assign ROW_NUMBER(), then filter on 'rank' in outer query.
  3. Final Answer:

    You cannot filter on an alias in the WHERE clause directly. -> Option D
  4. Quick Check:

    Filter on ROW_NUMBER() alias in outer query, not WHERE [OK]
Hint: Filter ROW_NUMBER() in outer query, not WHERE clause [OK]
Common Mistakes:
  • Using alias in WHERE clause directly
  • Thinking ORDER BY DESC is invalid
  • Adding unnecessary GROUP BY
5. You have a products table with columns category, product_id, and price. You want to find the top 2 most expensive products per category, but if two products have the same price, include both even if it means more than 2 per category. Which query achieves this?
hard
A. Use ROW_NUMBER() partitioned by category ordered by price DESC and filter WHERE rn <= 2.
B. Use RANK() partitioned by category ordered by price DESC and filter WHERE rank <= 2.
C. Use DENSE_RANK() partitioned by category ordered by price ASC and filter WHERE dense_rank <= 2.
D. Use COUNT() partitioned by category ordered by price DESC and filter WHERE count <= 2.

Solution

  1. Step 1: Understand difference between ROW_NUMBER(), RANK(), and DENSE_RANK()

    ROW_NUMBER() assigns unique ranks, so ties get different numbers. RANK() assigns same rank to ties and skips ranks after. DENSE_RANK() assigns same rank to ties but no gaps.
  2. Step 2: Choose RANK() to include ties at second place

    RANK() allows including all products tied at rank 2, possibly more than 2 rows per category.
  3. Final Answer:

    Use RANK() partitioned by category ordered by price DESC and filter WHERE rank <= 2. -> Option B
  4. Quick Check:

    RANK() includes ties with same rank [OK]
Hint: Use RANK() to include ties in top N per group [OK]
Common Mistakes:
  • Using ROW_NUMBER() excludes ties
  • Using DENSE_RANK() includes ties but no gaps
  • Using COUNT() is invalid for ranking
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard