CMD Guide
HomeDatabasesSQL Practice Problems

Sellers With No Sales

A LEFT JOIN keeps every seller and attaches their 2020 orders; the rows that come back with O.seller_id IS NULL are exactly the sellers for whom no matching 2020 order existed — that is the anti-join, and it only works because the year condition lives in the ON clause where it filters what counts as a match, not in WHERE where it would filter rows after the join has already happened.

The problem

Given Seller(seller_id, seller_name) and Orders(order_id, sale_date, order_cost, customer_id, seller_id), report the names of every seller who made no sale in 2020, ordered by seller_name ascending.

The phrase "made no sale" is the tell: you are looking for absence of a row. There is no column that says "this seller had no orders" — that fact only exists as a gap, and the canonical way to surface a gap in SQL is the LEFT-JOIN / IS NULL anti-join.

The data we will trace

Four sellers; the Orders table has 2019 and 2020 rows. Watch Frank: he sold in 2019 but not 2020.

Seller
seller_idseller_name
1Daniel
2Elizabeth
3Frank
4William
order_idseller_idsale_date
112020-03-01
222020-05-02
322020-06-03
432019-09-04
542020-01-25

Daniel, Elizabeth, William all have a 2020 order. Frank's only order is 2019-09-04. Expected answer: Frank.

The query

SELECT S.seller_name
FROM   Seller AS S
       LEFT JOIN Orders AS O
              ON  S.seller_id = O.seller_id
              AND YEAR(O.sale_date) = '2020'
WHERE  O.seller_id IS NULL
ORDER  BY S.seller_name;

Two conditions sit in ON: the join key and the year. WHERE does one thing only — keep the rows that failed to match.

Step-by-step trace

Step 1 — LEFT JOIN with both conditions in ON

For each seller, SQL looks for an Orders row where seller_id matches and the year is 2020. A seller with no such row still survives, padded with NULLs (that is what LEFT JOIN guarantees).

S.seller_idS.seller_nameO.seller_idO.sale_date
1Daniel12020-03-01
2Elizabeth22020-05-02
2Elizabeth22020-06-03
3FrankNULLNULL
4William42020-01-25

Frank's 2019 order did not satisfy YEAR(O.sale_date)='2020', so it was never a match — Frank is kept as a NULL-padded row. That NULL is the signal we are about to read.

Step 2 — WHERE O.seller_id IS NULL

Keep only rows where the right side came back empty:

S.seller_nameO.seller_id
FrankNULL

Step 3 — SELECT + ORDER BY

Project seller_name, sort ascending. One row remains:

seller_name
Frank
diagram
diagram

Why the naive version is wrong

The tempting "clean up" is to move the year into WHERE:

-- BROKEN: silently behaves like an INNER JOIN
SELECT S.seller_name
FROM   Seller AS S
       LEFT JOIN Orders AS O ON S.seller_id = O.seller_id
WHERE  YEAR(O.sale_date) = '2020'   -- ✖ wrong place
   AND O.seller_id IS NULL;          -- ✖ can never be satisfied

Trace it against Frank. The ON now matches only on id, so Frank joins to his real 2019 row — O.sale_date = 2019-09-04, not NULL. Then WHERE runs:

So the two WHERE conditions are mutually exclusive: a row can't simultaneously have YEAR(sale_date)='2020' (needs a non-NULL date) and O.seller_id IS NULL. The query returns empty. The fix is mechanical: filters on the outer (right) table of a LEFT JOIN belong in ON, because ON decides what counts as a match before padding happens; only the IS NULL probe belongs in WHERE.

Pitfalls

Takeaways


Based on LeetCode 1607 "Sellers With No Sales." Join semantics (ON vs WHERE for outer joins, NULL-padding) follow the SQL standard as documented in the PostgreSQL manual ("Joined Tables") and Markus Winand's SQL Performance Explained / use-the-index-luke.com on sargability. NULL-comparison and three-valued-logic behavior per the SQL:2016 standard. Re-authored and deepened for this guide to make the ON-vs-WHERE anti-join mechanism explicit, with a corrected broken-variant walkthrough and a side-by-side query-plan diagram.

🤖 Don't fully get this? Learn it with Claude

Stuck on Sellers With No Sales? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Sellers With No Sales** (Databases) and want to truly understand it. Explain Sellers With No Sales from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Sellers With No Sales** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Sellers With No Sales** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Sellers With No Sales** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes