Skip to content
intermediatePhase 22 · SQL Subqueries

EXISTS and NOT EXISTS

Check for existence of rows using EXISTS.

30m
3 problems
Topic Progress0%

EXISTS

EXISTS

The EXISTS operator tests whether a subquery returns any rows. It returns TRUE if at least one row is found, FALSE otherwise. EXISTS is used in WHERE and HAVING clauses and is typically paired with correlated subqueries.

-- Find all customers who have placed at least one order
SELECT customer_id, customer_name
FROM customers c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);

The SELECT 1 inside EXISTS is a convention. You could select any expression since EXISTS only cares about row existence, not the actual values. Some developers prefer SELECT * for clarity, but SELECT 1 is marginally more efficient.

-- Find departments that have at least one employee earning over $100,000
SELECT d.department_id, d.department_name
FROM departments d
WHERE EXISTS (
    SELECT 1
    FROM employees e
    WHERE e.department_id = d.department_id
    AND e.salary > 100000
);

EXISTS is particularly efficient because the database engine can stop scanning the subquery table as soon as it finds the first matching row. This short-circuit behavior makes EXISTS faster than IN or JOIN for existence checks, especially on large tables with proper indexes.

NOT EXISTS

NOT EXISTS

NOT EXISTS is the opposite of EXISTS. It returns TRUE when the subquery returns no rows. This is the standard way to implement anti-joins — finding rows in one table that have no matching rows in another.

-- Find customers who have never placed an order
SELECT customer_id, customer_name
FROM customers c
WHERE NOT EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);

NOT EXISTS is safer than NOT IN because it handles NULLs correctly. When the subquery returns NULLs, NOT IN returns no rows, but NOT EXISTS works as expected.

-- Find products that have never been reviewed
SELECT p.product_id, p.product_name
FROM products p
WHERE NOT EXISTS (
    SELECT 1
    FROM reviews r
    WHERE r.product_id = p.product_id
);

You can combine EXISTS and NOT EXISTS for complex filtering:

-- Find customers who have placed orders but never left a review
SELECT c.customer_id, c.customer_name
FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
)
AND NOT EXISTS (
    SELECT 1 FROM reviews r WHERE r.customer_id = c.customer_id
);

NOT EXISTS is the recommended pattern for anti-joins in production code due to its NULL safety and generally better performance.

EXISTS vs IN

EXISTS vs IN

Both EXISTS and IN can solve similar problems, but they work differently. IN compares a value against a list of values. EXISTS checks for row existence with a correlated subquery.

-- Using IN
SELECT customer_id, customer_name
FROM customers
WHERE customer_id IN (SELECT customer_id FROM orders);

-- Using EXISTS
SELECT customer_id, customer_name
FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);

Key differences:

Feature IN EXISTS
Subquery type Returns a list of values Returns rows (correlated)
NULL handling NOT IN fails with NULLs Handles NULLs correctly
Performance Good for small lists Better for large tables with indexes
Readability Simpler for value matching More explicit for existence checks
-- IN is simpler when matching literal values
SELECT * FROM products
WHERE category_id IN (1, 2, 3);

-- EXISTS is better for cross-table existence
SELECT * FROM products p
WHERE EXISTS (
    SELECT 1 FROM inventory i WHERE i.product_id = p.product_id AND i.quantity > 0
);

Use IN for simple value matching against a known set. Use EXISTS when checking for related rows in another table, especially when NULLs might be involved.

When to Use Each

When to Use Each

Choosing between EXISTS, IN, and JOINs depends on the specific scenario, data distribution, and database engine.

Use EXISTS when:

  • Checking for existence of related rows in another table
  • The subquery is correlated and the tables are large
  • NULL values might be present in the subquery results
  • You need anti-join patterns with NOT EXISTS
-- Best use case for EXISTS
SELECT * FROM orders o
WHERE EXISTS (
    SELECT 1 FROM returns r WHERE r.order_id = o.order_id
);

Use IN when:

  • Matching against a static list of values
  • The subquery returns a small, finite set
  • You want simpler, more readable code
-- Best use case for IN
SELECT * FROM products
WHERE category_id IN (SELECT category_id FROM featured_categories);

Use JOIN when:

  • You need columns from the related table
  • You want to count or aggregate related rows
  • Performance is critical and an index exists
-- Best use case for JOIN
SELECT o.order_id, COUNT(r.return_id) AS return_count
FROM orders o
LEFT JOIN returns r ON o.order_id = r.order_id
GROUP BY o.order_id;

In practice, EXISTS and JOINs are often interchangeable. Start with the most readable approach, then optimize based on performance profiling.

Practice Problems

0/3solved
Customers with No Orders

Write a query to find all customers who have never placed an order using NOT EXISTS.

Solution
SELECT customer_id, customer_name
FROM customers c
WHERE NOT EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);
Products in Stock

Write a query to find all products that have at least one unit in stock using EXISTS.

Solution
SELECT product_id, product_name
FROM products p
WHERE EXISTS (
    SELECT 1
    FROM inventory i
    WHERE i.product_id = p.product_id
    AND i.quantity > 0
);
Customers with Orders but No Reviews

Write a query to find customers who have placed at least one order but have never written a review. Use both EXISTS and NOT EXISTS.

Solution
SELECT c.customer_id, c.customer_name
FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
)
AND NOT EXISTS (
    SELECT 1 FROM reviews r WHERE r.customer_id = c.customer_id
);

Quiz

1. What does EXISTS return if the subquery returns no rows?

Question 1 options

2. Why is NOT EXISTS safer than NOT IN?

Question 2 options

3. What should you SELECT inside an EXISTS subquery?

Question 3 options

4. What is the primary purpose of SQL EXISTS?

Question 4 options

Flashcards

Question

What does the EXISTS operator do?

Answer

Tests whether a subquery returns any rows. Returns TRUE if at least one row exists, FALSE otherwise.

Question

When should you use NOT EXISTS over NOT IN?

Answer

Always prefer NOT EXISTS for anti-joins because it handles NULLs correctly and typically performs better on large datasets with proper indexes.

Question

What is an anti-join?

Answer

A join pattern that returns rows from one table that have no matching rows in another table. Implemented using NOT EXISTS or LEFT JOIN with IS NULL.

Question

What is SQL EXISTS?

Answer

SQL EXISTS is a key concept in SQL databases.

Question

When to use SQL EXISTS?

Answer

Use SQL EXISTS when building production systems that require reliability, scalability, and maintainability.

Revision Notes

Key Takeaways

  • 1.EXISTS returns TRUE if the subquery has any rows, FALSE otherwise
  • 2.NOT EXISTS is the preferred anti-join pattern — safer than NOT IN with NULLs
  • 3.SELECT 1 is conventional inside EXISTS; any expression works
  • 4.Use EXISTS for existence checks, IN for value matching, JOINs for column access

Interview Tips

  • Explain why NOT EXISTS is preferred over NOT IN
  • Show the anti-join pattern using NOT EXISTS
  • Discuss EXISTS short-circuit behavior for performance
  • Compare EXISTS, IN, and JOINs for the same problem

Cheat Sheet

EXISTS Cheat Sheet

EXISTS

SELECT * FROM t1
WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.id);

NOT EXISTS

SELECT * FROM t1
WHERE NOT EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.id);

EXISTS vs IN

  • EXISTS: better for large tables, handles NULLs, uses correlation
  • IN: simpler for static value lists, good for small sets

Anti-Join Patterns

-- NOT EXISTS (preferred)
SELECT * FROM customers c WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);

-- LEFT JOIN + IS NULL
SELECT c.* FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id WHERE o.customer_id IS NULL;