EXISTS vs IN
Two ways to check for related rows — and when each one wins
EXISTS vs IN
Both EXISTS and IN check whether related rows exist in another table — but they work differently under the hood, and the difference matters for both correctness and performance. Here's when to use which:
WHERE country IN ('India', 'Japan', 'Australia').WHERE id NOT IN (1, 2, NULL) returns zero rows. Comparing anything to NULL yields unknown, and NOT unknown is still unknown. NOT EXISTS doesn't have this problem — it's always NULL-safe.EXISTS vs IN
Two approaches to checking related rows
- IN: collect all values, check membership
- EXISTS: check row-by-row, stop at first match
- NOT IN with NULLs = 0 rows (the most famous SQL bug)
- NOT EXISTS is always NULL-safe
The NOT IN + NULL Trap
This bug costs companies millions
- WHERE id NOT IN (1, 2, NULL) → returns ZERO rows
- NULL makes every NOT IN comparison UNKNOWN
- NOT EXISTS doesn't have this problem — always safe
- Always prefer NOT EXISTS over NOT IN
-- BROKEN: returns 0 rows if any customer_id is NULL
WHERE id NOT IN (SELECT customer_id FROM orders)
-- SAFE: always works correctly
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
)Performance Comparison
When speed matters
- EXISTS short-circuits at first match → faster for large sets
- IN materializes the full list → OK for small static lists
- Most optimizers rewrite IN → semi-join (same plan as EXISTS)
- NOT EXISTS typically faster than NOT IN (NULL overhead)
Decision Rules
Quick guide to choosing
- Small literal list → IN:
WHERE status IN ('a','b','c') - Large subquery → EXISTS: correlated + short-circuit
- NOT found check → ALWAYS NOT EXISTS (NULL-safe)
- If in doubt → EXISTS is the safer default
INChecks membership in a list of valuesEXISTSTrue if correlated subquery returns any rowsNOT EXISTSNULL-safe way to find unmatched rows| id | name |
|---|---|
| C1001 | Aarav Sharma |
| C1002 | Sara Chen |
| C1003 | James Wilson |
| C1004 | Maria Garcia |
| C1005 | Yuki Tanaka |
| C1006 | Priya Patel |
| C1007 | Alex Johnson |
| C1008 | Chen Wei |
| C1009 | Emma Brown |
| C1010 | Omar Hassan |
| C1011 | Lena Muller |
| C1012 | Ravi Kumar |
| product_id | customer_id | rating |
|---|---|---|
| P101 | C1001 | 5 |
| P101 | C1003 | 4 |
| P101 | C1005 | 3 |
| P102 | C1002 | 5 |
| P102 | C1006 | 4 |
| P103 | C1004 | 4 |
| P104 | C1007 | 5 |
| P105 | C1009 | 5 |
| P105 | C1012 | 4 |
| P107 | C1008 | 3 |
| P108 | C1011 | 5 |
| P109 | C1001 | 5 |
| id | name | category | price | stock |
|---|---|---|---|---|
| P101 | Wireless Mouse | Electronics | 24.99 | 150 |
| P102 | Office Desk | Furniture | 189 | 25 |
| P103 | LED Desk Lamp | Furniture | 45.5 | 80 |
| P104 | USB-C Cable | Electronics | 12.99 | 500 |
| P105 | Bluetooth Speaker | Electronics | 79 | 60 |
| P106 | Office Chair | Furniture | 95 | 15 |
| P107 | Coffee Maker | Appliances | 89.99 | 40 |
| P108 | Notebook Set | Stationery | 8.5 | 200 |
| P109 | Wireless Headphones | Electronics | 159 | 30 |
| P110 | Standing Desk | Furniture | 425 | 8 |
Worked Example
Find products that have received at least one 5-star review using EXISTS.
| name |
|---|
| Bluetooth Speaker |
| Notebook Set |
| Office Desk |
| USB-C Cable |
Key Concepts
Pro Tip
Choosing between IN and EXISTS affects both correctness (NULL behavior) and performance. NOT IN with NULLs is one of the most common SQL bugs.
When to Use
Checking if customers have orders (EXISTS), filtering by a value list (IN), finding unmatched records (NOT EXISTS).
Find all customers who have NEVER left a review. Use NOT EXISTS. Show the customer name.