Step 1 Solve it yourself first
Tables you will use
customers orders order_items products
What it really tests
"Every" questions — — which can't be answered with a plain . They test counting distinct matches against a total, or thinking in double negatives.
The pattern: "All of" (relational division)
"Every", "all of the", "each one of", "the complete set".
Picture it
flowchart LR
C["Stationery has 2 products<br/>Pen, Notebook"] --> Q{"customer bought<br/>2 different ones?"}
Q -->|"Priya: Notebook and Pen"| K["kept"]
Q -->|"everyone else: 0"| X["dropped"]The approach, step by step
- Count how many products the category has (here 2: Pen and Notebook).
- For each customer, count the different products from that category they have bought.
- Keep customers whose count equals the category's total: with
COUNT(DISTINCT …)= (). - Or say it as a double negative: there is no Stationery product this customer has not bought.
Your turn: write it before you look
Your queries in this box
Show the solution
Stuck for more than ten minutes? Open it, read it, close it, and write it again from memory.
Solution: count distinct matches and compare with the total
SELECT c.name
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id
WHERE p.category = 'Stationery'
GROUP BY c.customer_id, c.name
HAVING COUNT(DISTINCT p.product_id) = (
SELECT COUNT(*) FROM products WHERE category = 'Stationery'
)
ORDER BY c.name;1 row
| name |
|---|
| Priya |
Priya bought notebooks in two orders and pens in one; stops the repeat notebook counting twice.
Another way: The double negative: no product they haven't bought
SELECT c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM products p
WHERE p.category = 'Stationery'
AND NOT EXISTS (
SELECT 1
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.customer_id = c.customer_id
AND oi.product_id = p.product_id
)
)
ORDER BY c.name;1 row
| name |
|---|
| Priya |
Read it as: keep the customer if there is no Stationery product that this customer has not bought.
Edge cases to say out loud
- An empty category: the counting version returns nobody, while the double-negative version returns every customer ("bought all of nothing"). Ask which is wanted.
- Buying the same product twice must not count twice: use
COUNT(DISTINCT). - Products added to the category later change the answer for past customers — that is usually intended.
Common mistakes
COUNT(*)instead ofCOUNT(DISTINCT product_id), so two notebooks look like two different products.- Hard-coding the total (= 2) instead of counting the category.
- Using ('Pen', 'Notebook'), which finds customers who bought any of them, not all.
Step 2 Go further (free, optional)
Optional. The notes above are written to be enough on their own. These free resources give a second explanation and more practice.
-
Practice
PostgreSQL Exercises — Aggregation
pgexercises.com, free
-
Practice
DataLemur — relational division questions
real interview questions, many free
Step 3 Practise
Try each one yourself first: out loud, or written and run in the practice box below, which saves every query you run. Then open the answer and compare.
-
ExplainSay what it does and when to use it, in plain words, with no notes.
Explain the double version in plain words.
Show answer
It turns "bought every product" into a double negative: "there is no Stationery product that this customer has not bought".
• The outer NOT EXISTS goes through each Stationery product, looking for one this customer is missing.
• The inner NOT EXISTS answers, for one product: "has this customer never bought it?" (no item line of theirs has that product).
• If the search finds no missing product, the customer has bought them all, and they are kept.It reads oddly, but it works without counting, and it handles products bought several times naturally.
-
Writefrom a blank page, then run it
Find customers who bought every product in the Electronics category. (Who bought the phone case?)
Show answer
Change the category in both places. Nobody qualifies: Electronics has two products, the headphones and the phone case, and nobody has ever bought the phone case. Priya and Tom bought headphones, but one product out of two isn't "every product". An empty result is the right answer.
sqlSELECT c.name FROM customers c JOIN orders o ON o.customer_id = c.customer_id JOIN order_items oi ON oi.order_id = o.order_id JOIN products p ON p.product_id = oi.product_id WHERE p.category = 'Electronics' GROUP BY c.customer_id, c.name HAVING COUNT(DISTINCT p.product_id) = ( SELECT COUNT(*) FROM products WHERE category = 'Electronics' ) ORDER BY c.name;No rows
name -
Predictsay it before you run it
If Tom had also bought a pen and a notebook, what would the solution return?
Show answer
Priya and Tom. Stationery is the pen and the notebook. Priya already bought both; if Tom also bought one of each, he would have 2 distinct Stationery products too, matching the category's 2. Below, they are added to his order 104.
sqlINSERT INTO order_items VALUES (104, 1, 1), (104, 2, 1); SELECT c.name FROM customers c JOIN orders o ON o.customer_id = c.customer_id JOIN order_items oi ON oi.order_id = o.order_id JOIN products p ON p.product_id = oi.product_id WHERE p.category = 'Stationery' GROUP BY c.customer_id, c.name HAVING COUNT(DISTINCT p.product_id) = ( SELECT COUNT(*) FROM products WHERE category = 'Stationery' ) ORDER BY c.name;2 rows
name Priya Tom -
Edge casesSay what happens with NULLs, ties, duplicates or no rows at all, and fix it.
Show what each version returns for a category with no products, and explain why they differ.
Show answer
Take a category with no products, such as 'Toys'.
• The version returns nobody. Its keeps only item lines in that category, and there are none, so there are no groups for HAVING to compare with 0.
• The double NOT EXISTS version returns every customer (below). "Is there a Toys product this customer hasn't bought?" can never be yes when there are no Toys products, so every customer passes.Logically, the NOT EXISTS answer is the correct one: "bought every product in an empty set" is vacuously true, like "all my unicorns are pink". In practice, nobody wants that report, so the honest answer is to ask what the business wants for an empty category, and to handle it on purpose, for example by returning nothing when the category has no products.
sqlSELECT c.name FROM customers c WHERE NOT EXISTS ( SELECT 1 FROM products p WHERE p.category = 'Toys' AND NOT EXISTS ( SELECT 1 FROM orders o JOIN order_items oi ON oi.order_id = o.order_id WHERE o.customer_id = c.customer_id AND oi.product_id = p.product_id ) ) ORDER BY c.name;6 rows
name Lena Omar Priya Tom Tom Zoe
Practice box: write and run your answers here
Your queries in this box
Step 4 Interview-ready, then prove it
Your interview answer
About 20 to 40 seconds, said out loud.
This is relational division — "bought all of". I join each customer to the category's products they bought, group by customer, and keep those whose count of distinct products equals the category's product count from a subquery. An equivalent formulation is a double NOT EXISTS: there's no product in the category that the customer hasn't bought. I'd mention the empty-category case, where the two versions disagree.
Follow-up questions
How would you find customers who bought at least 2 of the 3 products in a category?
Same , with HAVING COUNT(DISTINCT p.product_id) >= 2.
How would you find customers who bought exactly the Stationery products and nothing else?
Count distinct stationery products = category size, and also count of non-stationery products = 0 ( over all their items).
Tick each check when you can do it without notes
A topic is finished when you pass all four checks, not when you have read it. Be strict: if you had to look at the notes, it is not ticked yet.
- Explain Say what it does and when to use it, in plain words, with no notes.
- Write Write the query from a blank page, then run it to check.
- Predict Read a query and say which rows it returns before you run it.
- Edge cases Say what happens with NULLs, ties, duplicates or no rows at all, and fix it.
0 of 4 checks ticked.
Saved in this browser. Back it up or move it to another device.