Intermediate: combining and changing data · Subqueries, CTEs and set operations · topic 27 of 62 · 35 min
IN, EXISTS and NOT EXISTS
Explanation
Subqueries that return a list
In module 2 you wrote city IN ('Pune', 'Mumbai') with a typed list. The list can also come from a subquery that returns one column and any number of rows. "Customers who have placed an order" means: customers whose id is in the list of customer_ids in orders.
SELECT id, name
FROM customers
WHERE id IN (SELECT customer_id FROM orders);
-- 1 Asha Verma, 2 Ravi Kumar, 3 Priya Nair, 4 Imran Khan, 5 Sneha PatilThat last point is a real difference from a join. orders JOIN customers would repeat Asha three times. IN (and EXISTS) never multiply rows; each outer row is either kept once or dropped.
Subqueries can be nested. Products that were part of a Delivered order: products whose id is in the order lines whose order_id is in the Delivered orders.
SELECT name FROM products
WHERE id IN (SELECT product_id FROM order_items
WHERE order_id IN (SELECT id FROM orders WHERE status = 'Delivered'));
-- Basmati Rice 5kg, Toor Dal 1kg, Steel Tiffin Box, Cotton Kurta, Running Shoes, Bluetooth EarbudsEXISTS: is there at least one?
EXISTS (subquery) is true when the subquery returns at least one row, and false when it returns none. The values in the row do not matter, which is why people write SELECT 1 inside. The subquery usually refers to the outer row (here c.id), which makes it a correlated subquery; topic 3 explains those in detail.
SELECT c.id, c.name
FROM customers AS c
WHERE EXISTS (SELECT 1 FROM orders AS o WHERE o.customer_id = c.id);
-- the same 5 customersNOT EXISTS: are there none?
SELECT d.id, d.name
FROM departments AS d
WHERE NOT EXISTS (SELECT 1 FROM employees AS e WHERE e.dept_id = d.id);
-- 5 | LegalThe NOT IN + NULL trap
Try the same question with NOT IN: departments whose id is not in the list of employees' dept_ids.
SELECT name FROM departments
WHERE id NOT IN (SELECT dept_id FROM employees);
-- (no rows!)Why? Deepak's dept_id is NULL, so the list is (1, 1, 1, 1, 2, 2, 2, 3, 3, NULL, 4). id NOT IN (list) means id <> 1 AND id <> 2 AND ... AND id <> NULL. For Legal, 5 <> NULL is unknown, and true AND unknown is unknown, not true. So no department ever passes. One NULL in the list empties the whole result.
| Form | NULL in the subquery | Advice |
|---|---|---|
| IN | Harmless for finding matches | Fine to use |
| NOT IN | Returns no rows at all | Avoid, or add WHERE col IS NOT NULL inside |
| NOT EXISTS | Handled correctly | The safe default for "has none" |
| LEFT JOIN ... IS NULL | Handled correctly | Also safe (module 5) |
SELECT name FROM departments
WHERE id NOT IN (SELECT dept_id FROM employees WHERE dept_id IS NOT NULL);
-- LegalCommon mistakes
- NOT IN with a column that can hold NULL. Use NOT EXISTS, or filter out the NULLs inside the subquery.
- Returning two columns from an IN subquery: id IN (SELECT customer_id, status ...) is an error. Return one column.
- Forgetting to link an EXISTS subquery to the outer row. WHERE EXISTS (SELECT 1 FROM orders) is true for every customer as long as any order exists.
- Using a JOIN when you only need "has at least one", then fixing the duplicates with DISTINCT. EXISTS expresses the question directly.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
IN, EXISTS and NOT EXISTS · 1/7
A subquery can return a whole list. IN checks whether a value is in it.
Prefer a video by a teacher? Find free videos on “sql exists not exists in subquery not in null trap” on YouTube ↗
In real life: Customers to remind about their cart
Shopping apps send reminders to people who added items to a cart but have not ordered in the last week. That is a NOT EXISTS: customers for whom there exists a cart item, and there does not exist a recent order. Teams prefer NOT EXISTS here because a single NULL customer id in the orders table would make a NOT IN version silently send no reminders at all.
SELECT c.name
FROM customers AS c
WHERE NOT EXISTS (
SELECT 1 FROM orders AS o
WHERE o.customer_id = c.id AND o.order_date >= '2025-03-01'
);Practice (0/4 done)
Which customers have placed at least one order? Use IN or EXISTS (not a join). Return id and name.
Which departments have no employees? Use NOT EXISTS. Return the department id and name.
The starter query should list departments with no employees, but it returns nothing, because one employee has a NULL dept_id. Keep NOT IN, but fix the query so it returns the right department. Return one column: name.
Which products were part of at least one Delivered order? Use nested IN subqueries (products → order_items → orders). Return one column: name.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Correlated subqueries and derived tablesSubqueries, CTEs and set operations · same module
- Common table expressions (WITH)Subqueries, CTEs and set operations · same module
- Subqueries, CTEs and window functionsBackend Development · SQL in depth
- UPDATE and DELETE safelyChanging data, transactions and ACID
- SELECT and WHERE: finding rowsBackend Development · Databases and SQL basics
- IN, BETWEEN, LIKE and NULLSELECT: filtering and sorting
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
The subquery SELECT dept_id FROM employees returns 1, 2, 3, 4 and one NULL. How many rows does WHERE id NOT IN (that subquery) return from departments (ids 1 to 5)?
Answer: 0
id <> NULL is unknown, so the NOT IN is never true for any row.
What does EXISTS (subquery) test?
Answer: Whether the subquery returns at least one row
The values do not matter, only whether any row comes back.
Asha has three orders. How many times does she appear in SELECT name FROM customers WHERE id IN (SELECT customer_id FROM orders)?
Answer: Once
IN keeps or drops each outer row once; it never multiplies rows like a join.
Which is the safest way to find customers who have never ordered, if orders.customer_id might contain NULLs?
Answer: WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id)
NOT EXISTS is not affected by NULLs in the subquery.
WHERE EXISTS (SELECT 1 FROM orders) without any link to the outer row is true...
Answer: for every outer row, as long as the orders table has any row
Without o.customer_id = c.id, the subquery does not depend on the customer.
A correlated subquery is slow on a table of 10 lakh rows. What is a good first step?Related
Answer: Check the plan, index the linking column, or rewrite it as a join to a grouped derived table
In principle it runs once per outer row; an index or a rewrite helps.
A scalar subquery returns no rows. What value does it give?Related
Why is NOT EXISTS often safer than NOT IN?Related
Answer: NOT IN with a NULL in the list returns no rows at all
x NOT IN (..., NULL) is never true, so the query silently returns nothing. NOT EXISTS has no such trap.
In SQLite, how do you see how many rows the last UPDATE changed?Related
Answer: SELECT changes()
changes() returns the rows changed by the most recent INSERT, UPDATE or DELETE.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Subqueries, CTEs and set operations” before its exam: Correlated subqueries and derived tables, Common table expressions (WITH), Recursive CTEs, UNION, INTERSECT and EXCEPT.
- Module 6 exam: subqueries, CTEs and set operations · 14 questions · 25 min, without help. 6 topics of this module still to finish first.
- Then the next module, Window functions. 62 topics left in the course before the final exam.