What this quiz covers
This quiz focuses on Ranking Functions, giving you a quick way to practice the rules, question types, and explanations that matter most for SQL.
An Orders table contains these rows: for customer C1, order O1 has amount 200 and date 2026-06-02, order O2 has amount 200 and date 2026-06-01, and order O3 has amount 150 and date 2026-06-03. For customer C2, order O4 has amount 300 and date 2026-06-01, and order O5 has amount 100 and date 2026-06-02.
The query assigns ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC, order_date ASC) AS rn. What row numbers are assigned to O1 and O5, respectively?
O1 receives 1, and O5 receives 2O1 receives 2, and O5 receives 5O1 receives 2, and O5 receives 2O1 receives 1, and O5 receives 1SQL Quiz
Practice Ranking Functions in SQL with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.
This quiz focuses on Ranking Functions, giving you a quick way to practice the rules, question types, and explanations that matter most for SQL.
Try each quiz question before looking at the correct answer. Use the explanations to review missed ideas, then come back to similar questions until the pattern feels familiar.
An Orders table contains these rows: for customer C1, order O1 has amount 200 and date 2026-06-02, order O2 has amount 200 and date 2026-06-01, and order O3 has amount 150 and date 2026-06-03. For customer C2, order O4 has amount 300 and date 2026-06-01, and order O5 has amount 100 and date 2026-06-02.
The query assigns ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC, order_date ASC) AS rn. What row numbers are assigned to O1 and O5, respectively?
O1 receives 1, and O5 receives 2O1 receives 2, and O5 receives 5O1 receives 2, and O5 receives 2 (correct answer)O1 receives 1, and O5 receives 1ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...), remember two things: the window function resets its counter for each partition, and it assigns strictly unique sequential integers based on the specified sort order.
Here, the data is partitioned by customer_id, so C1 and C2 are ranked independently. Within each partition, rows are sorted by amount DESC first, then order_date ASC as a tiebreaker.
For C1 (orders O1, O2, O3): sorting by amount descending gives O1 (200) and O2 (200) tied, then O3 (150). The tiebreaker order_date ASC puts O2 (June 1) before O1 (June 2). So the ranking is: O2 → rn=1, O1 → rn=2, O3 → rn=3.
For C2 (orders O4, O5): O4 has amount 300, O5 has amount 100. So O4 → rn=1, O5 → rn=2.
This confirms C: O1 receives 2 and O5 receives 2.
Answer A is wrong because it assumes O1 is the top-ranked row in C1's partition, ignoring that O2 ties O1 on amount but sorts earlier by date. Answer B is wrong on both counts — O5 receiving 5 would only make sense if row numbering continued globally across partitions, which it doesn't. Answer D incorrectly gives O5 a rank of 1, which belongs to O4 since 300 > 100.
The key study tip: always trace the ORDER BY clause step by step, including tiebreakers — they frequently determine whether a row ranks first or second, and exam questions are designed around exactly those edge cases.A query assigns ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) to employees. Several employees in the same department can have identical salaries, and no other ordering column is specified.
Which change is sufficient to guarantee stable row-number assignments for unchanged data, assuming employee_id is unique within each department?
employee_id to the window ordering after salary DESC (correct answer)ROW_NUMBER() with RANK() while keeping the same orderingORDER BY department_id, salary DESC to the queryPARTITION BY department_id with an outer grouping operationROW_NUMBER() assigns a unique integer to each row, but when tied rows exist and the ordering doesn't fully distinguish them, the database engine can arbitrarily break those ties, producing different orderings across executions.
The fix is to make the ORDER BY clause within the window fully deterministic. Since employee_id is unique within each department, appending it after salary DESC gives the engine a tiebreaker that always resolves the same way. Every row gets a predictable, stable rank — which is exactly what A accomplishes. The ordering becomes ORDER BY salary DESC, employee_id, and ties in salary no longer produce ambiguous numbering.
B is wrong because RANK() doesn't solve the problem — it actually moves in the opposite direction. RANK() assigns the same number to tied rows, so it sidesteps the uniqueness issue but doesn't guarantee stable numbering; it still uses the same non-deterministic ordering clause.
C is a common trap. Adding ORDER BY department_id, salary DESC to the outer query controls the display order of results but has no effect on how the window function internally assigns row numbers. Window ordering and query-level ordering are independent.
D misunderstands the purpose of PARTITION BY. Replacing it with a GROUP BY aggregation would collapse rows, destroying the row-level detail that ROW_NUMBER() is designed to annotate.
Study tip: Whenever you see ROW_NUMBER() and the word "stable" or "deterministic," immediately check whether the window ORDER BY includes a unique column — that's the only guaranteed fix.Four attempts are ranked using RANK() OVER (ORDER BY score DESC, completion_seconds ASC). Attempt A has score 90 and time 30; attempts B and D each have score 90 and time 25; attempt C has score 85 and time 20.
What ranks are assigned to attempts A and C, respectively?
A receives rank 2, and C receives rank 3A receives rank 3, and C receives rank 3A receives rank 2, and C receives rank 4A receives rank 3, and C receives rank 4 (correct answer)RANK(), two rules govern everything: ties receive the same rank, and the next rank skips a number equal to the count of tied rows. The ORDER BY score DESC, completion_seconds ASC means higher scores rank better, and among equal scores, faster times rank better.
Let's sort the four attempts: B and D both have score 90 and time 25 — they're the fastest among the 90s, so they tie for rank 1. A has score 90 but time 30 (slower), placing it next. Because two rows already occupy positions 1 and 2, A receives rank 3. Finally, C has a lower score of 85, so it falls after all the 90s. Since rank 3 was just used (only one row, A), the next rank skips to 4 — giving C rank 4.
That confirms D as the correct answer.
Choice A incorrectly gives A rank 2, ignoring that B and D both occupy the top positions, pushing A to rank 3. Choice B gets A's rank right at 3 but assigns C rank 3 as well — C cannot share rank 3 with A because C has a lower score, making it strictly worse. Choice C gives A rank 2 (same error as A) but correctly identifies C at rank 4.
A good study tip: always count how many rows are tied above a given row to determine where RANK() lands. Since two rows tie at rank 1, the next assigned rank is 1 + 2 = 3, not 2 — RANK() always gaps, while DENSE_RANK() would not.For a row in one partition, a query ordered by metric DESC reports RANK() = 7 and DENSE_RANK() = 4. No other information about the partition is available.
What can be concluded with certainty about rows preceding this row in the window order?
RANK() and DENSE_RANK() is essential. RANK() tells you how many rows have a strictly higher value than the current row, plus one. DENSE_RANK() tells you how many distinct values are strictly higher, plus one.
So when you see RANK() = 7 and DENSE_RANK() = 4, you can extract concrete information. Since RANK() = 7, there are exactly 6 rows preceding this row with a higher metric (7 − 1 = 6). Since DENSE_RANK() = 4, there are exactly 3 distinct metric values among those preceding rows (4 − 1 = 3). This perfectly matches answer A: six rows precede it, representing three distinct higher metric values. This also logically implies ties exist among those 6 rows — 6 rows collapsed into only 3 distinct values.
Answer B is wrong on both counts — it claims 7 preceding rows and 4 distinct values, which would correspond to RANK() = 8 and DENSE_RANK() = 5. Answer C gets the row count right (6) but misidentifies the distinct value count as 4, confusing DENSE_RANK() = 4 with the number of distinct values rather than subtracting 1. Answer D inverts the two values entirely, swapping the roles of RANK() and DENSE_RANK() — a classic trap when students haven't fully internalized what each function measures.
A reliable memory trick: both functions use a "plus one" convention, so always subtract 1 from each to get the raw counts. RANK() − 1 = preceding rows; DENSE_RANK() − 1 = distinct preceding values.A query runs SELECT employee_id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM Employees; and does not include an outer ORDER BY clause.
Which statement correctly describes what the query guarantees?
OVER (ORDER BY salary DESC) clause tells SQL how to rank or number rows within the window calculation — it determines which row gets rn = 1, which gets rn = 2, and so on. However, this ordering instruction lives entirely inside the window function and has no effect on the final result set's display order. Without an explicit outer ORDER BY clause at the query level, the database engine is free to return rows in any physical order it chooses — typically whatever is most efficient for execution. This makes B correct: the row numbers are meaningful and accurately reflect descending salary order, but the sequence in which those rows appear on your screen is not guaranteed.
A is wrong because it conflates window ordering with result-set ordering — these are separate mechanisms, and one does not imply the other. C goes too far in the other direction: the row numbers are not arbitrary. They are deterministically assigned based on salary, so calling the row-number assignment arbitrary is incorrect. D introduces a false condition — distinct salary values are relevant only if you're considering ties with RANK() or DENSE_RANK(), not the physical display order guarantee, which remains undefined regardless of uniqueness.
A practical tip: whenever you see OVER (ORDER BY ...), ask yourself whether there is also a query-level ORDER BY. If not, treat the result order as unpredictable, even if the window values look sorted.Within one department, employee salaries are 120000, 120000, 110000, and 100000. A report must return every employee whose salary is in the two highest distinct salary levels.
Which ranking expression and filter reliably satisfy the requirement, including the 110000 employee despite the tie at the highest salary?
ROW_NUMBER() OVER (ORDER BY salary DESC) and filter for values at most 2RANK() OVER (ORDER BY salary DESC) and filter for values at most 2DENSE_RANK() OVER (ORDER BY salary DESC) and filter for values at most 2 (correct answer)ROW_NUMBER() OVER (ORDER BY salary ASC) and filter for values at most 2ROW_NUMBER() assigns a unique sequential number to every row, breaking ties arbitrarily. RANK() gives tied rows the same rank but then skips the next rank (two employees tied at rank 1 produce no rank 2 — the next value jumps to rank 3). DENSE_RANK() gives tied rows the same rank and never skips — the next distinct value always gets the next consecutive rank.
Given salaries of 120000, 120000, 110000, and 100000, DENSE_RANK() ORDER BY salary DESC assigns rank 1 to both 120000 earners, rank 2 to the 110000 earner, and rank 3 to the 100000 earner. Filtering for dense_rank <= 2 correctly captures all three employees in the top two salary levels — exactly what the requirement asks for. C is correct.
A fails because ROW_NUMBER() assigns unique values 1, 2, 3, 4. Filtering for <= 2 returns only two employees, arbitrarily excluding one 120000 earner and the 110000 earner entirely.
B uses RANK(), which assigns rank 1, 1, 3, 4. Filtering for <= 2 returns only the two 120000 employees — the 110000 earner sits at rank 3 and is excluded, missing the second distinct salary level.
D orders salaries ascending, so the lowest salaries get the smallest ranks — this returns the two cheapest employees, the opposite of what's needed.
Your study tip: memorize the trio as ROW_NUMBER = unique, RANK = gaps, DENSE_RANK = no gaps. Any question mentioning "distinct levels" or "tiers" is almost always signaling DENSE_RANK().A report must return at most three sales rows per region, choosing the rows with the greatest sale_amount. Ties may be broken by sale_id, so exactly three rows should be returned from each region that has at least three rows.
Which query structure correctly implements the requirement in standard SQL processing order?
SELECT region, sale_id, sale_amount FROM Sales WHERE ROW_NUMBER() OVER (PARTITION BY region ORDER BY sale_amount DESC, sale_id) <= 3SELECT region, sale_id, sale_amount FROM Sales GROUP BY region, sale_id, sale_amount HAVING ROW_NUMBER() OVER (PARTITION BY region ORDER BY sale_amount DESC, sale_id) <= 3SELECT region, sale_id, sale_amount FROM (SELECT region, sale_id, sale_amount, ROW_NUMBER() OVER (PARTITION BY region ORDER BY sale_amount DESC, sale_id) AS rn FROM Sales) s WHERE rn <= 3 (correct answer)SELECT region, sale_id, sale_amount FROM (SELECT region, sale_id, sale_amount, RANK() OVER (PARTITION BY region ORDER BY sale_amount DESC) AS rn FROM Sales) s WHERE rn <= 3ROW_NUMBER(), the critical concept to remember is SQL's logical processing order: FROM → WHERE → GROUP BY → HAVING → SELECT. Window functions are evaluated during the SELECT phase, which means you cannot reference them in WHERE or HAVING clauses of the same query level — they don't exist yet at those stages.
Option C is correct because it wraps the window function inside a subquery (the inner FROM clause), fully resolving ROW_NUMBER() before the outer WHERE rn <= 3 filter is applied. This respects SQL's processing order: the subquery computes row numbers first, and the outer query filters on that already-computed value. The PARTITION BY region ORDER BY sale_amount DESC, sale_id ensures exactly three rows per region, with ties broken cleanly by sale_id.
Option A fails because it tries to use ROW_NUMBER() directly inside WHERE, which is illegal — the window function hasn't been evaluated at the WHERE processing stage. Option B makes the same mistake with HAVING, and also incorrectly uses GROUP BY on individual columns when no aggregation is needed. Putting a window function in HAVING doesn't save you — HAVING filters aggregated groups, not window-function results. Option D uses RANK() instead of ROW_NUMBER(), which is structurally similar to C but breaks the requirement. RANK() can return more than three rows per region when there are ties in sale_amount, violating the "at most three rows" guarantee.
Study tip: Whenever you need to filter on a window function result, your instinct should be "subquery first, filter second." If you see a window function in WHERE or HAVING without a subquery, that's an automatic red flag.A Results table contains four rows in one division with scores 100, 90, 90, and 80. The query calculates ROW_NUMBER(), RANK(), and DENSE_RANK() over ORDER BY score DESC.
Which values are assigned to the row whose score is 80?
ROW_NUMBER = 4, RANK = 3, and DENSE_RANK = 3ROW_NUMBER = 4, RANK = 4, and DENSE_RANK = 3 (correct answer)ROW_NUMBER = 3, RANK = 4, and DENSE_RANK = 3ROW_NUMBER = 4, RANK = 4, and DENSE_RANK = 4ROW_NUMBER = 4, RANK = 4, DENSE_RANK = 3.
Choice A is wrong because it assigns RANK = 3, which would only be correct if RANK didn't skip — that's actually DENSE_RANK behavior.
Choice C is wrong on two counts: ROW_NUMBER cannot be 3 here (that belongs to the second score-90 row), and RANK would still be 4, not 4 being the issue but ROW_NUMBER being incorrect.
Choice D is wrong because it assigns DENSE_RANK = 4, confusing it with RANK — DENSE_RANK never skips, so it can't reach 4 with only three distinct score values.
Memory tip: Think Dense = No gaps, RANK = skips like a skipped grade in school.A competition has scores 95, 95, 90, 85, and 85. The following value is calculated for each competitor: RANK() OVER (ORDER BY score DESC) AS position. An outer query keeps rows where position <= 2.
Which set of competitors is returned?
95 (correct answer)95 and the competitor at 9095, 90, and both competitors at 8595 and the competitor at 90RANK(), the key concept to internalize is how ties are handled and what rank numbers are actually assigned.
RANK() assigns the same rank to tied rows, then skips subsequent ranks. So with scores 95, 95, 90, 85, 85, the assignments are: both competitors at 95 receive rank 1, the competitor at 90 receives rank 3 (not 2, because rank 2 is skipped), and both competitors at 85 receive rank 4. When your outer query filters WHERE position <= 2, only rows with rank 1 or rank 2 qualify — meaning only the two competitors who scored 95 are returned, since no competitor actually holds rank 2. That confirms A is correct.
Choice B is the most tempting trap — you might assume rank 2 belongs to the 90 scorer, but RANK() skips numbers after a tie, so 90 lands at rank 3, which fails the <= 2 filter. Choice C describes a scenario where every competitor is returned, which would only make sense if the filter were removed entirely or set much higher. Choice D reflects confusion about how ties work — it implies only one of the two 95 scorers is returned, but since both share rank 1, both pass the filter equally.
A useful rule of thumb: RANK() can produce gaps in rank numbers, while DENSE_RANK() never skips. If this question used DENSE_RANK(), the 90 scorer would hold rank 2 and would be included. Always identify which ranking function is used before reasoning about which rows a filter will capture.A CTE first groups sales by department and produces totals: department A has 500, departments B and C each have 400, and department D has 100. A second query calculates both RANK() OVER (ORDER BY total DESC) and DENSE_RANK() OVER (ORDER BY total DESC) over those grouped rows.
Which pair of rankings is assigned to department D?
RANK = 3 and DENSE_RANK = 3RANK = 4 and DENSE_RANK = 3 (correct answer)RANK = 4 and DENSE_RANK = 4RANK = 3 and DENSE_RANK = 4RANK() skips position numbers after a tie, while DENSE_RANK() never skips — it always increments by exactly one.
Here's how the four departments sort by total descending: A (500) is 1st, then B and C are tied at 400, and D sits last at 100. Both RANK() and DENSE_RANK() assign 1 to department A. For the tie between B and C, both functions assign 2 to each. Now the behavior diverges: RANK() has already "used up" positions 2 and 3 for the tied pair, so the next row — department D — receives rank 4. DENSE_RANK(), however, simply moves to the next consecutive rank after 2, giving D a dense rank of 3. That makes the correct answer B.
Choice A is wrong because it assigns RANK = 3 to D, ignoring that the tie between B and C consumes two rank positions (2 and 3), pushing D to position 4. Choice C incorrectly assigns DENSE_RANK = 4, which would only happen if there were no ties at all — dense rank never skips. Choice D reverses the logic entirely, swapping the values of the two functions, which reflects a fundamental misunderstanding of how each works.
A reliable memory trick: think of DENSE_RANK as counting distinct rank levels (no gaps), while RANK counts actual row positions (gaps appear after ties). On SQL exams, tie-handling questions almost always hinge on this single difference.