Himanshu SharmaSoftware engineer
Let’s talk

MySQL · Lesson 28 of 50

EXISTS, ANY and ALL

Test for the existence of related rows with EXISTS and NOT EXISTS, compare against sets with ANY, SOME and ALL, and see why NOT EXISTS beats NOT IN with NULLs.

12 minCombining tablesUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonUse EXISTS and NOT EXISTS to test for related rows, and ANY or ALL to compare a value with a set of values.

Often you do not need the related rows themselves. You only need to know whether any exist. "Which customers have placed an order?" does not require listing the orders. EXISTS answers that, and it is usually written with a correlated subquery, which links the inner query to the current outer row.

sql
SELECT c.id, c.first_name
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id)
ORDER BY c.id;
result
+----+------------+
| id | first_name |
+----+------------+
|  1 | Asha       |
|  2 | Ben        |
|  3 | Chloe      |
|  4 | Daniel     |
|  5 | Elena      |
|  6 | Farid      |
|  7 | Grace      |
|  8 | Hiro       |
| 10 | Jack       |
| 11 | Kavya      |
+----+------------+

For each customer the inner query looks for at least one matching order. As soon as it finds one, EXISTS is true and MySQL stops looking. That early exit can make it faster than a join that has to produce every pair. The SELECT 1 is a convention: the inner select list is ignored, so many people write a constant.

Unlike a join, EXISTS never duplicates the outer rows. Asha has four orders but appears once.

NOT EXISTS: rows with no relatives

NOT EXISTS is true when the subquery returns nothing. This finds customers who never ordered:

sql
SELECT c.id, c.first_name
FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
result
+----+------------+
| id | first_name |
+----+------------+
|  9 | Isabel     |
| 12 | Liam       |
+----+------------+

Products that have never been ordered:

sql
SELECT p.id, p.name
FROM products p
WHERE NOT EXISTS (SELECT 1 FROM order_items oi WHERE oi.product_id = p.id);
result
+----+--------------------+
| id | name               |
+----+--------------------+
| 15 | Puzzle 1000 Pieces |
+----+--------------------+

Both could also be written with LEFT JOIN ... IS NULL. The NOT EXISTS form reads more like the English question and avoids joining at all.

Why not NOT IN?

NOT IN looks similar but has a trap with NULL. The employees table has a NULL manager_id (the chief executive). Ask: which employees are not anyone's manager?

sql
SELECT name FROM employees WHERE id NOT IN (SELECT manager_id FROM employees);

It returns nothing, which is wrong: several people manage nobody. The list contains a NULL, so id NOT IN (..., NULL) is never true. Fix it either by removing NULLs from the list, or by using NOT EXISTS:

sql
SELECT e.name
FROM employees e
WHERE NOT EXISTS (SELECT 1 FROM employees r WHERE r.manager_id = e.id)
ORDER BY e.id;
result
+-------------+
| name        |
+-------------+
| Paula Gomez |
| Quinn Baker |
| Rina Das    |
| Sam Wright  |
| Tara Singh  |
| Uma Reddy   |
| Victor Lang |
+-------------+

Now it correctly lists everyone who has no direct reports. This is the main reason experienced developers prefer NOT EXISTS.

EXISTS with extra conditions

The subquery can have any conditions. Customers who have a delivered order in 2024 of more than one product line:

sql
SELECT c.id, c.first_name
FROM customers c
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.customer_id = c.id AND o.status = 'delivered'
    AND (SELECT COUNT(*) FROM order_items oi WHERE oi.order_id = o.id) > 1
)
ORDER BY c.id;
result
+----+------------+
| id | first_name |
+----+------------+
|  1 | Asha       |
|  2 | Ben        |
|  4 | Daniel     |
|  6 | Farid      |
|  8 | Hiro       |
| 10 | Jack       |
| 11 | Kavya      |
+----+------------+

ANY, SOME and ALL

These operators compare a value with the set of values a subquery returns. They sit after a comparison operator.

  • x > ANY (subquery): true if x is greater than at least one value (that is, greater than the smallest).
  • x > ALL (subquery): true if x is greater than every value (greater than the largest).
  • = ANY is the same as IN. <> ALL is the same as NOT IN.
  • SOME is a synonym for ANY.

Products more expensive than every book:

sql
SELECT name, price FROM products WHERE price > ALL (SELECT price FROM products WHERE category_id = 2) ORDER BY price;
result
+-----------------------------+--------+
| name                        | price  |
+-----------------------------+--------+
| Building Blocks Set         |  49.99 |
| Standing Desk Mat           |  55.00 |
| Mechanical Keyboard         |  89.50 |
| Noise-Cancelling Headphones | 199.99 |
+-----------------------------+--------+

Products cheaper than at least one product in Electronics (so cheaper than the dearest one):

sql
SELECT name, price FROM products WHERE price < ANY (SELECT price FROM products WHERE category_id = 1) AND category_id <> 1 ORDER BY price DESC LIMIT 3;
result
+-----------------------------+-------+
| name                        | price |
+-----------------------------+-------+
| Standing Desk Mat           | 55.00 |
| Building Blocks Set         | 49.99 |
| Data Structures Illustrated | 45.00 |
+-----------------------------+-------+

These are not used often, because MAX and MIN express the same idea more directly (price > (SELECT MAX(price) ...)), but you will see them in older code and interview questions. Be careful when the subquery returns no rows: > ALL over an empty set is true for every row, while > ANY over an empty set is false.

Common mistakes

  • Forgetting to correlate the subquery. EXISTS (SELECT 1 FROM orders) without a link to the outer row is true for everyone, as long as the orders table has any row.
  • Using NOT IN against a column that can contain NULL.
  • Selecting many columns in the EXISTS subquery. Harmless but noisy; use SELECT 1.
  • Confusing ALL and ANY in the direction of the comparison.

Exercise

Write queries to find: (1) customers who have at least one order with status shipped, (2) categories that contain no product ordered in June 2024, and (3) employees who earn more than all employees in the Sales department (department 2).

Show one possible answer
sql
SELECT c.id, c.first_name FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.status = 'shipped');

SELECT cat.id, cat.name FROM categories cat
WHERE NOT EXISTS (
  SELECT 1 FROM products p
  JOIN order_items oi ON oi.product_id = p.id
  JOIN orders o ON o.id = oi.order_id
  WHERE p.category_id = cat.id AND o.order_date >= '2024-06-01' AND o.order_date < '2024-07-01'
);

SELECT name, salary FROM employees
WHERE salary > ALL (SELECT salary FROM employees WHERE department_id = 2);
result
+----+------------+
| id | first_name |
+----+------------+
|  1 | Asha       |
|  3 | Chloe      |
|  6 | Farid      |
| 10 | Jack       |
+----+------------+
+----+------+
| id | name |
+----+------+
|  3 | Home |
+----+------+
+-------------+-----------+
| name        | salary    |
+-------------+-----------+
| Meera Iyer  | 150000.00 |
| Nina Petrov | 125000.00 |
+-------------+-----------+

Key points

  • EXISTS is true when its subquery returns at least one row. It does not care about the values, only whether any row exists.
  • NOT EXISTS finds rows with no related rows and, unlike NOT IN, is not fooled by NULLs.
  • x > ANY (subquery) means greater than at least one value; x > ALL (subquery) means greater than every value.
  • In an EXISTS subquery write SELECT 1; the selected columns are ignored.

Check yourself

1. When is WHERE EXISTS (subquery) true?
2. Why is NOT EXISTS safer than NOT IN for finding customers with no orders?
3. What does price > ALL (SELECT price FROM products WHERE category_id = 2) mean?
4. Which operator is a synonym for ANY?