CMD Guide
HomeDatabases

SQL Practice Problems

Step 12 in the Databases path · 63 concepts · 24 problems

0 / 87 complete

📘 Learn SQL Practice Problems from zero

Every problem in this set rewards the same disciplined process. Don't start typing SQL — start by reading and restating. Say out loud: "What is one row of my output, and what does it represent?" That single sentence fixes your output granularity and is the most common thing weak candidates get wrong.

Next, nail the schema and constraints. Note the primary key of each table (it tells you what's already unique — e.g. (player_id, event_date) means one login row per player per day), which columns can be NULL, and whether a join is one-to-one or one-to-many (a fan-out join silently double-counts your sums). Skim the expected-output sample — it tells you the granularity and whether zero-count rows must appear.

Then build the brute-force shape first: which table is the spine, what joins hang off it, what filter narrows rows. Get a correct (if naive) query running mentally before optimizing. Now spot the pattern from the recognition list: is this a per-group aggregate, a top-per-group window, an anti-join, a range-join weighted average, or a CASE bucketing? Map the signal to the technique — and resist over-reaching (a bare "max per group" wants MAX()...GROUP BY, not a window).

Optimize and clean: push filters into WHERE before grouping, replace correlated subqueries with joins or window functions, and use HAVING only for post-aggregate conditions.

Finally, walk the edge cases: empty groups, ties in a ranking (do you want RANK or ROW_NUMBER?), NULLs in join keys or COUNT/AVG, integer division, and duplicate rows. Mentally test with a tiny 2-3 row example and trace it through every clause. State your assumptions to the interviewer — clarifying granularity and null-handling out loud signals senior-level rigor.

✨ Added by the guide to build intuition — not from the source course.

🎯 Guided practice

Let's walk through Game Play Analysis II (return the device a player used on their first login). I'll think aloud about how the signal selects the technique.

  1. Restate & granularity. "One row per player, showing the device_id of their earliest login." Table Activity(player_id, device_id, event_date, games_played), with PK (player_id, event_date) — so a player has at most one row per day. One row per player tells me I'm collapsing many login rows down to one — an aggregate or ranking job.
  2. Spot the trap. The naive instinct is GROUP BY player_id with MIN(event_date). That's exactly the right shape for Game Play Analysis I (which only wants first_login). But here I need device_id from that same first row. A plain GROUP BY can't return a non-aggregated device_id tied to the min date — that's the classic "select a column from the group's extreme row" problem.
  3. Signal → technique. "Pick the row with the earliest date per player, keeping its other columns" is the textbook top-per-group pattern → a window function: ROW_NUMBER() OVER (PARTITION BY player_id ORDER BY event_date). Partition by the entity (player), order by the tie-breaking key (date), filter to rank 1.
  4. Optimal shape. SELECT player_id, device_id FROM (SELECT player_id, device_id, ROW_NUMBER() OVER (PARTITION BY player_id ORDER BY event_date) AS rn FROM Activity) t WHERE rn = 1;
  5. Edge cases. Could a player have two first-day rows? The PK (player_id, event_date) forbids it, so the earliest date is unique and ROW_NUMBER is safe. If ties were possible and you wanted all tied rows you'd use RANK; if you wanted exactly one you'd keep ROW_NUMBER and add a deterministic tie-breaker to the ORDER BY. No NULL dates here, so ordering is safe.

The transferable lesson: the moment you need "a column from the min/max row per group," your hand should move to ROW_NUMBER() ... PARTITION BY ... ORDER BY. But if you only need the extreme value itself and no companion column, stay with plain GROUP BY MIN()/MAX() — that's all Bikes Last Time Used (just MAX(end_time) per bike) and Game Play Analysis I (just MIN(event_date)) require. Knowing which side of that line a prompt falls on is the real skill the set is training.

✨ Added by the guide — work these before the full problem set.

Lessons in this topic

🧠 Review & recall

Active recall is what moves a topic into long-term memory. Flip each card before revealing, then test yourself — your results are saved on this device.

Flashcard
Before writing any SQL for a practice problem, what is the single most important question to answer first?
tap to reveal →
Ask: 'What is one row of my output, and what does it represent?' That fixes your output granularity, which weak candidates most often get wrong, and tells you whether you are collapsing rows (aggregate/ranking) or filtering them.
💡 One row = one ___? Say it out loud first.
Flashcard
You need to return a non-aggregated column (like device_id) from the earliest/min row per group. Which technique, and why not plain GROUP BY?
tap to reveal →
Use ROW_NUMBER() OVER (PARTITION BY entity ORDER BY key) and keep rn = 1. A plain GROUP BY with MIN(date) can return the extreme value but cannot return a companion column tied to that extreme row.
💡 'A column FROM the min/max row' -> hand moves to ROW_NUMBER + PARTITION BY.
Flashcard
When does a bare 'max/min per group' want plain GROUP BY MIN()/MAX() instead of a window function?
tap to reveal →
When you only need the extreme value itself and no companion column from that row, e.g. Bikes Last Time Used (MAX(end_time) per bike) or Game Play Analysis I (MIN(event_date) per player). Don't over-reach to a window function.
💡 Value only -> GROUP BY. Value + sidekick column -> window.
Flashcard
What is the LEFT JOIN ... IS NULL anti-join pattern, as used in Sellers With No Sales?
tap to reveal →
LEFT JOIN the spine table to the other table, push extra filters (like YEAR(sale_date)=2020) into the ON clause, then WHERE the joined key IS NULL to keep only rows with no match. This isolates 'has no related row' cases.
💡 Anti-join = LEFT JOIN then keep the NULLs.
Flashcard
How do you select students/entities that match ALL items in a reference set (Students Who Attended All Courses)?
tap to reveal →
GROUP BY the entity and use HAVING COUNT(DISTINCT key) = (SELECT COUNT(*) FROM ReferenceTable). DISTINCT guards against duplicate rows inflating the count.
💡 'Attended ALL' -> HAVING COUNT(DISTINCT)=total.
Flashcard
What is the gaps-and-islands trick used in Longest Winning Streak to count consecutive wins?
tap to reveal →
Map win->0 and non-win->1, take a running SUM() OVER (PARTITION BY player ORDER BY day) to label streak segments, then GROUP BY that running total and take MAX of the per-group win counts as the longest streak.
💡 Win=0, break=1, running sum groups the islands.
Flashcard
In the index's clause-discipline advice, where do filters go relative to grouping, and what is HAVING for?
tap to reveal →
Push row filters into WHERE before grouping; use HAVING only for post-aggregate conditions (e.g. on COUNT). Replace correlated subqueries with joins or window functions where possible.
💡 WHERE before, HAVING after (aggregates only).
Q1. For '2nd Highest Salary', what does the canonical query SELECT DISTINCT salary FROM Employee ORDER BY salary DESC LIMIT 1 OFFSET 1 rely on, and what does it return if there is no second salary?
Q2. School Top Achievers by Subject wants students in the 'top three UNIQUE scores' per subject (ties share a rank, no gaps swallow slots). Which window function fits?
Q3. To report sellers who made NO sales in 2020 using a LEFT JOIN, where must the YEAR(sale_date) = 2020 condition go?
Q4. A prompt asks only for each player's first_login date (a single value, no other column from that row). What is the recommended approach?
Q5. In the Running Total for Different Genders solution, what makes the self-join ON s1.gender = s2.gender AND s1.day >= s2.day produce a CUMULATIVE total per day?