SELECT WHERE NOT EXISTS
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
NOT EXISTS is the standard SQL way to return rows from one table only when no matching row exists in another table. It expresses an anti-join clearly, handles NULL safely, and is often easier for the optimizer to reason about than more error-prone alternatives.
Basic Pattern
The outer query selects candidate rows. The correlated subquery checks whether a related row exists for the current candidate. NOT EXISTS keeps the outer row only when the subquery returns zero rows.
This returns customers who have no orders.
The SELECT 1 inside the subquery is conventional. The subquery is not returning data to the outer query; it is only answering yes or no.
How the Correlation Works
The important part is the predicate that ties the inner query to the current row of the outer query:
Without that correlation, you are no longer asking "Does this customer have an order?" You are asking "Does the orders table contain any row at all?" That is a completely different query.
Common Anti-Join Use Cases
Finding records with no related rows is the most common use:
It is also useful in idempotent insert patterns:
This ensures the insert happens only when the row is missing.
NOT EXISTS vs NOT IN
NOT IN can look similar, but it behaves badly when the subquery can return NULL.
If orders.customer_id contains NULL, the three-valued logic of SQL can make the whole predicate evaluate in surprising ways. NOT EXISTS avoids that trap because it checks for matching rows directly instead of comparing against a list with possible null semantics.
NOT EXISTS vs LEFT JOIN ... IS NULL
This pattern is also common:
It often produces the same result, and many optimizers transform both forms into similar plans. Still, NOT EXISTS usually reads closer to the intent: keep rows for which no related row exists.
Performance Considerations
NOT EXISTS is usually efficient when the join predicate is index-friendly. For the customer and orders example, an index on orders.customer_id is the main thing that matters.
With the right index, the database can often stop searching as soon as it finds the first match for a given outer row, because existence is all it needs to know.
Common Pitfalls
- Forgetting the correlation predicate and accidentally checking whether the inner table has any rows at all.
- Rewriting the query as
LEFT JOIN ... IS NULLand then testing the wrong inner column. - Using
NOT INon a nullable subquery column and getting unexpectedNULL-driven logic. - Assuming the subquery output columns matter when only existence is being tested.
- Ignoring indexes on the correlation columns and then blaming
NOT EXISTSfor poor performance.
Summary
- '
NOT EXISTSis the canonical SQL anti-join pattern.' - It returns outer rows only when the correlated subquery finds no match.
- It is safer than
NOT INwhenNULLvalues are possible. - It often expresses intent more clearly than
LEFT JOIN ... IS NULL. - Good indexes on the correlation columns are the main performance requirement.
Related reading
- Selecting a row of pandas series/dataframe by integer index
- Selecting columns from 3D tensor according to a 1D tensor of indices Tensorflow
- selecting rows with id from another table
- selecting unique values from a column
- Selecting loss and metrics for Tensorflow model
- Selecting loss and metrics for Tensorflow model
- Selection of database
- Send settings to clickhouse via http protocol using requests

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.