Question: What does EXISTS do in SQL?
Answer: EXISTS is a filter that checks whether a subquery returns at least one row. It does not care what the subquery selects; it only cares whether a match exists. This makes it great for “does a related row exist?” questions, like customers who have placed an order.
Interview-Ready Answer: I use EXISTS when I want to test for presence, not count. It returns true as soon as the subquery finds one matching row, so it is a natural fit for semi-join logic like “customers who have orders” or “rows that have a related record.” One important detail is that the selected columns inside the subquery usually do not matter; the database only needs to know whether a row exists.
Detailed Explanation: EXISTS is a boolean predicate, which means it answers a true/false question inside WHERE, HAVING, or another condition. Think of it as a tiny bouncer at the door: it only asks, “Is there at least one matching row in that subquery?” If yes, let the outer row pass; if not, reject it.
EXISTS becomes true and can stop checking more rows.EXISTS is false.This “stop at the first match” behavior is the key idea. It is why EXISTS often feels fast and natural for presence checks.
EXISTS when you only need to know whether related rows exist.NOT EXISTS for anti-matches, such as “customers with no orders.”Comparison: Many candidates confuse EXISTS with IN or JOIN. They solve related problems, but they are not identical.
| Feature | EXISTS | IN | JOIN |
|---|---|---|---|
| Goal | Presence check | Value match | Combine rows |
| Duplicates | Ignored | May matter | Can multiply rows |
| NULL risk | Low | Can be tricky | Depends on join |
| Best for | Related existence | Small lists | Data merging |
In optimizer terms, databases often turn EXISTS into a semi-join, which is a join that only asks whether a match exists instead of returning matching child rows. With a good index on the correlated column, each lookup may be close to an indexed probe, often around O(log n) per outer row in a B-tree index. Without an index, a naive plan can behave more like O(parent × child), which is where slow queries come from.
Realistic numbers: on a table with 1 million customers and 10 million orders, an index on orders.customer_id can turn “does this customer have an order?” from a full scan into a quick lookup. Without that index, the database may inspect far too many rows.
SELECT 1 is a common style because it makes the intent obvious.NULL values can make IN surprising, but EXISTS is usually safer because it checks rows, not membership in a list with three-valued logic.NOT EXISTS is the safest way to express “no related rows,” especially when the child column may contain NULL.Memory Hook: “One match is enough.” Picture a guest list where the bouncer stops reading the list the moment he sees your name once.
Real-World Story: Imagine a checkout service for an online store. Before sending a discount coupon, the system needs to know whether a customer has ever placed an order. The query uses EXISTS so each customer is checked against the orders table, and the database stops at the first matching order instead of counting all orders.
What goes wrong when people misunderstand it? A developer may write a JOIN and forget that a customer with five orders will appear five times. In production, that can cause duplicate coupon emails, duplicate rows in a report, or inflated customer counts. The symptom is often easy to spot: logs show repeated customer IDs, dashboards suddenly jump, and support tickets mention users receiving multiple messages when only one was expected.
-- Runnable demo of EXISTS and NOT EXISTS.
-- This works in many SQL engines that support standard DDL/DML.
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
customer_name VARCHAR(50) NOT NULL
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NULL,
order_total DECIMAL(10,2) NOT NULL
);
INSERT INTO customers (customer_id, customer_name) VALUES
(1, 'Ava'),
(2, 'Ben'),
(3, 'Chen');
INSERT INTO orders (order_id, customer_id, order_total) VALUES
(101, 1, 49.99),
(102, 1, 19.99),
(103, 2, 75.00),
(104, NULL, 12.50); -- Orphan row: useful to show why EXISTS is safer than some NULL-sensitive patterns.
-- 1) Customers who have at least one order.
-- We use SELECT 1 because the subquery only needs to prove a row exists.
SELECT c.customer_id, c.customer_name
FROM customers AS c
WHERE EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.customer_id
)
ORDER BY c.customer_id;
-- 2) Customers with no orders.
-- This is the classic NOT EXISTS anti-join pattern.
SELECT c.customer_id, c.customer_name
FROM customers AS c
WHERE NOT EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.customer_id
)
ORDER BY c.customer_id;
-- 3) Edge case: the orphan order with customer_id = NULL does not match anyone.
-- EXISTS checks for matching rows, so NULL in the child key does not create a false match.
SELECT o.order_id, o.customer_id, o.order_total
FROM orders AS o
WHERE EXISTS (
SELECT 1
FROM customers AS c
WHERE c.customer_id = o.customer_id
)
ORDER BY o.order_id;Follow-up & Tricky Questions:
EXISTS different from IN? EXISTS checks whether a matching row exists, while IN compares a value against a list of returned values. EXISTS is usually safer when NULL values or large child tables are involved.SELECT 1 inside EXISTS? Because the column list is ignored by the predicate, so SELECT 1 makes the intent clear and avoids pretending the value matters.NOT EXISTS mean? It returns true when the subquery produces zero rows. This is the standard way to ask for rows that have no related match.o.customer_id = c.customer_id. That link is what lets EXISTS test each outer row independently.EXISTS? Yes. Many engines rewrite it as a semi-join or anti-semi-join, which often allows index-driven lookup and early exit after the first match.EXISTS (SELECT NULL ...) behave differently from EXISTS (SELECT 1 ...)? No. The selected expression does not matter; only whether at least one row is returned matters.EXISTS care about duplicates in the subquery? No. Once one row is found, the answer is already true, so duplicates do not change the result.NOT EXISTS always the same as LEFT JOIN ... IS NULL? Often they are equivalent for the intended logic, but NOT EXISTS is usually clearer and less error-prone when the join condition or null handling gets complicated.Common Mistakes:
JOIN when you only need presence. Fix: use EXISTS so you do not accidentally duplicate outer rows.NOT EXISTS for missing-related-row queries. Fix: prefer NOT EXISTS over fragile NOT IN patterns when NULL may appear.orders.customer_id.Memory Hook: One match is enough. If the subquery finds a single row, EXISTS is true immediately.
Cheat Sheet:
EXISTS = “at least one row?”NOT EXISTS = “zero related rows?”SELECT 1 is common, but any select list works.Practice Tasks:
EXISTS, IN, and JOIN on a small dataset with duplicates and a NULL value.