What do EXISTS, IN, ANY and ALL return when the subquery returns no rows?
answer
- nothing here is UNKNOWN
- existential needs a witness; there is none
- universal has no counterexample
- think empty OR versus empty AND
- one of them passes every outer row
basics
~10 sOver an empty subquery: EXISTS is FALSE and NOT EXISTS TRUE; IN and any ANY predicate are FALSE; NOT IN and any ALL predicate are TRUE, vacuously. None of them is UNKNOWN.
solid answer
~50 sAn empty subquery gives definite answers everywhere. `EXISTS` is FALSE (no rows came back) and `NOT EXISTS` is TRUE. Existential predicates need at least one satisfying row, so `x IN (...)`, `x = ANY (...)` and `x > ANY (...)` are all FALSE. Universal predicates are **vacuously TRUE** — there is no counterexample — so `x NOT IN (...)`, `x <> ALL (...)` and `x > ALL (...)` are TRUE for every row. That last one bites in practice: a filter like `price > ALL (SELECT price FROM products WHERE category_id = 7)` silently passes every row when category 7 is empty, instead of returning nothing. Note the contrast with the aggregate rewrite: `price > (SELECT MAX(price) ...)` compares against NULL over an empty table and yields UNKNOWN, so it keeps no rows. Same intent, opposite behaviour on empty input.
code
sql · 9 lines-- If category 7 has no rows, this returns the ENTIRE products table
SELECT p.id
FROM products p
WHERE p.price > ALL (SELECT price FROM products WHERE category_id = 7);
-- The aggregate spelling returns NO rows in the same situation
SELECT p.id
FROM products p
WHERE p.price > (SELECT MAX(price) FROM products WHERE category_id = 7);go deeper
Know the easy half: EXISTS over an empty subquery is FALSE and NOT EXISTS is TRUE. Being able to say that IN fails when there is nothing to match is enough here.
Explain vacuous truth via the OR/AND fold, and give the four results for IN, NOT IN, ANY and ALL without hesitating. Stress that none of them is UNKNOWN.
Bring the production angle: a > ALL filter turns into a pass-through when its subquery empties out, so treat universal predicates over volatile sets as a wrong-answer risk and decide the empty-case behaviour explicitly.
The judgment call is which empty-set convention the codebase means by default, and how that intent is made visible — an explicit guard, a comment, or a house rule — so nobody rediscovers vacuous truth in an incident.
## The rule in one table With `S` an empty subquery result: | predicate | result | |---|---| | `EXISTS (S)` | FALSE | | `NOT EXISTS (S)` | TRUE | | `x IN (S)` / `x = ANY (S)` | FALSE | | `x NOT IN (S)` / `x <> ALL (S)` | TRUE | | `x > ANY (S)`, `x < ANY (S)`, … | FALSE | | `x > ALL (S)`, `x < ALL (S)`, … | TRUE | Nothing here is UNKNOWN. Emptiness is a definite fact about the subquery, so all of these predicates give definite truth values regardless of nullability. ## Why ANY is FALSE and ALL is TRUE Think of the quantifiers as folds over the set of comparisons. `ANY` is a big `OR`; the identity of `OR` is FALSE, so an empty `OR` is FALSE. `ALL` is a big `AND`; the identity of `AND` is TRUE, so an empty `AND` is TRUE. Logicians call the second case *vacuous truth*: the claim "every element of the empty set satisfies P" has no counterexample, so it holds. The everyday phrasing helps too. `x NOT IN (S)` asks "is x absent from S?" — if S is empty, x is certainly absent, so TRUE. `x IN (S)` asks "is x present?" — it cannot be, so FALSE. That much is intuitive. The counterintuitive one is inequality: `x > ALL (S)` asks "is x greater than every element?" and with nothing to be greater than, the answer is yes. ## The bug this causes ```sql -- Intent: only products pricier than everything in category 7 SELECT p.id, p.name FROM products p WHERE p.price > ALL (SELECT price FROM products WHERE category_id = 7); ``` If category 7 has been emptied — a data-loading failure, a soft-delete flag, a tenant that has no such category — the subquery returns nothing, the predicate is vacuously TRUE, and the query returns the **entire** products table. A filter that was meant to be restrictive becomes a pass-through. Because it produces plausible-looking rows instead of an error, this is a silent wrong-answer defect, and it is a good thing to check whenever a report suddenly grows. ## The aggregate rewrite behaves differently A natural rewrite of the same intent is: ```sql WHERE p.price > (SELECT MAX(price) FROM products WHERE category_id = 7) ``` Over an empty input, `MAX` returns NULL, so the comparison is `price > NULL` → UNKNOWN, and `WHERE` keeps nothing. So the two spellings of "pricier than everything in category 7" disagree exactly on the empty case: the quantified form returns all rows, the aggregate form returns none. Neither is wrong; they encode different answers to "what should happen when there is nothing to compare against?", and part of writing a correct query is deciding which one you mean and making it explicit — for instance by guarding with `EXISTS (SELECT 1 FROM products WHERE category_id = 7)` when the empty case must yield nothing. ## EXISTS is the easy one `EXISTS` over an empty result is FALSE and `NOT EXISTS` is TRUE, which matches how people read them out loud, so this case rarely surprises anyone. It also composes safely: an anti-filter written with `NOT EXISTS` keeps every outer row when the inner set is empty, which is exactly the intended meaning of "has no match". ## Empty is not the same as containing NULL It is worth separating two situations that both involve "missing" data. A subquery that returns *no rows* produces definite TRUE/FALSE answers as tabulated above. A subquery that returns *a NULL row* is a different case: there is something to compare against, the comparison is undecidable, and the predicate can land on UNKNOWN. `x NOT IN (empty)` is TRUE; `x NOT IN (NULL)` is UNKNOWN. Candidates who conflate the two give the wrong answer to both. ## What to remember Empty means *definite*, never UNKNOWN. Existential predicates fail on empty; universal predicates succeed on empty. If a universal predicate is doing real filtering work in production code, ask what should happen when its subquery is empty and write that intent down — either with an explicit guard or with a comment — because the vacuous-truth default is the opposite of what most readers assume.
- Why is `x > ALL (empty subquery)` TRUE rather than FALSE?Because ALL is a universal claim and an empty set offers no counterexample — this is vacuous truth. Mechanically, ALL folds the comparisons with AND, and the identity of AND is TRUE, so an empty fold is TRUE. The practical consequence is that such a filter stops filtering when its subquery empties out, returning every candidate row.
- How does `> ALL` over an empty subquery differ from `> (SELECT MAX(...))` over the same empty input?They give opposite results. `> ALL` is vacuously TRUE and keeps every row; `MAX` over no rows returns NULL, so `x > NULL` is UNKNOWN and WHERE keeps nothing. The two spellings encode different intentions about the empty case, so pick deliberately — and if the empty case must yield nothing, guard the query with an explicit EXISTS test.
- Can any of these predicates return UNKNOWN when the subquery is empty?No. UNKNOWN arises only from comparisons involving NULL values, and with no rows there are no comparisons to perform. Every one of EXISTS, IN, NOT IN, ANY and ALL resolves to a definite TRUE or FALSE over an empty result. Nullability of the subquery column is irrelevant in this case.
saying these in an interview costs you the question
- Says an empty subquery makes the predicate UNKNOWN
- Claims `> ALL` over nothing is FALSE
- Assumes ANY and ALL behave alike on empty input
- Thinks NOT IN over an empty subquery filters everything out
- Confuses an empty result with a result containing NULL