Practice

Join fundamentals: five drills

Five JOIN-only drills, ordered easy → hard. Each one isolates a single JOIN concept. The default editor shows a working solution. Try clearing it and writing your own first; the hidden checks will grade what's there when you press Run.

Tables: users, products, orders, reviews.

Task: list every product that has at least one order. Return id, name, and category, one row per product, ordered by id.

The trap: a naive SELECT p. FROM products p JOIN orders o ON o.product_id = p.id returns one row per order*, not per product.

Loading SQL editor...

2. LEFT JOIN: keep unmatched, count carefully (Easy) #

Task: list every product (even those that have never been ordered) with the number of orders it has. Return id, name, and order_count. Products with no orders should show 0, not NULL. Order by id.

The trap: COUNT(*) returns 1 for an unmatched LEFT JOIN row (the row exists, even if all the right-side columns are NULL). Use COUNT(o.id) to count only matched rows.

Loading SQL editor...

3. SELF JOIN: pairs from the same table (Medium) #

Task: find every ordered pair of paid orders by the same user, where the first order id is smaller than the second. Return user_id, first_order_id, second_order_id, ordered by user_id, first_order_id, second_order_id.

The trap: joining a table to itself without an asymmetric condition (a.id < b.id) yields each pair twice and self-pairs ((1, 1), (2, 2), …) once.

Loading SQL editor...

4. Multi-table chain with filter (Medium) #

Task: find users who left a 5-star review on a product they personally ordered. Return email, product_name, and comment. Order by email, then product_name.

The skill: chaining four tables (usersreviewsproducts and a separate join through orders) and using INNER JOIN as a filter — only users who actually ordered the reviewed product show up.

Loading SQL editor...

5. Anti-join: find what is missing (Hard) #

Task: find users who have placed at least one order but have never written a review. Return id and email, ordered by id.

The skill: anti-join. Two valid patterns — NOT EXISTS (usually clearest) and LEFT JOIN ... WHERE r.id IS NULL (the "missing match" idiom). Both should produce the same result; NOT EXISTS is generally more index-friendly on real engines.

Loading SQL editor...
Tip

The patterns to remember:

  • INNER JOIN = "rows where both sides match." Plain SELECT after a JOIN can fan out — use DISTINCT, GROUP BY, or EXISTS when the question is "which X has at least one Y".
  • LEFT JOIN = "all left rows, plus right where matched." Counting matches: use COUNT(right_table.id), never COUNT(*).
  • SELF JOIN = same table aliased twice. To avoid duplicates and self-pairs, use an asymmetric key condition (a.id < b.id).
  • Anti-join = NOT EXISTS (...) or LEFT JOIN ... WHERE r.id IS NULL. Equivalent results, different shapes; NOT EXISTS is usually the cleaner expression of intent.