SQL EXISTS and NOT EXISTS: Check Related Rows Safely

EXISTS is true when a subquery returns at least one row; NOT EXISTS is true when it returns no rows.

Lesson 32 of 102Beginner11 min read

EXISTS is true when a subquery returns at least one row; NOT EXISTS is true when it returns no rows.

Questions such as customers who have ordered or customers who have never ordered are relationship questions. EXISTS expresses them directly, without copying every matching sale into the output and without the NULL trap that can affect NOT IN.

This reference adds detail to the main course. You can read it independently: the small database below supplies every table and row used in the examples. Start with the question each query answers, then compare the returned values with the data rather than memorising a line of syntax.

Set up the practice database

Run this setup once in a new practice database, before the examples. It uses four customers and six sales. Ana has two sales, Ben has one, Cara has two, and Dev has none.

Sale 106 has no customer link; sale 104 has an unknown amount. These deliberate test cases expose missing information and unmatched relationships.

SQL
CREATE TABLE customers (
  id INTEGER PRIMARY KEY,
  name VARCHAR(40) NOT NULL,
  city VARCHAR(40),
  manager_id INTEGER
);
CREATE TABLE sales (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER,
  amount DECIMAL(10,2),
  status VARCHAR(20) NOT NULL,
  created_on DATE NOT NULL
);
INSERT INTO customers (id,name,city,manager_id) VALUES
  (1,'Ana','Leeds',NULL),(2,'Ben','Leeds',1),
  (3,'Cara','Porto',1),(4,'Dev',NULL,2);
INSERT INTO sales (id,customer_id,amount,status,created_on) VALUES
  (101,1,10,'paid','2026-01-01'),
  (102,1,30,'pending','2026-01-02'),
  (103,2,20,'paid','2026-01-02'),
  (104,3,NULL,'pending','2026-02-01'),
  (105,3,20,'paid','2026-02-02'),
  (106,NULL,5,'paid','2026-02-03');

An amount of zero would be a known value. The NULL amount here means the value is unknown. A customer with no matching sale differs from a sale whose customer_id is NULL.

The primary keys identify individual records even when amounts or cities repeat. Dates use year-month-day format. The examples specify sorting whenever order matters; insertion order alone does not guarantee display order.

How EXISTS and NOT EXISTS works

The subquery inside EXISTS supplies evidence that a qualifying row exists. Its selected values are normally irrelevant: SELECT 1 is a common convention because the question concerns rows rather than the value of the constant. EXISTS does not count how many qualifying rows there are. One match and a thousand matches both make it true, so the outer customer row appears once if the outer input itself contains that customer once.

A correlated subquery refers to the current outer row. In WHERE EXISTS (SELECT 1 FROM sales AS s WHERE s.customer_id = c.id), c identifies the customer being tested. The inner WHERE looks only for that customer’s sales. Omitting the correlation changes the question to whether the sales table has any row at all; if it does, every customer passes. Carefully read each qualified column to confirm which query level provides it.

NOT EXISTS and missing relationships

NOT EXISTS asks whether the qualifying set is empty. It is useful for customers without sales, products without stock movements, or records missing a required relationship. A NULL customer_id in an unrelated sale does not poison the entire test. Its equality comparison with a particular customer is unknown, so that inner row does not qualify, while other matching sales still qualify normally. This differs from NOT IN when the returned membership set contains NULL.

Place the business filters inside the subquery when they define the relationship you are testing. Customers without any paid sales are different from customers without any sales. Cara has a pending sale and a paid sale, so she passes a has-paid-sale test. A customer with pending sales only would pass has-sales and fail has-paid-sales. The optimiser may implement the query as a semi join or anti join; SQL’s meaning does not require physically running the inner statement from scratch for every outer row. Indexing the matching customer_id can help on larger data.

MySQL, PostgreSQL, SQL Server and SQLite

EXISTS and NOT EXISTS with correlated subqueries are supported by all four systems. SELECT 1 is a convention, not a requirement that the subquery produce exactly one row. Execution strategies differ; avoid claiming that an engine always checks rows in a specific physical order or always performs one separate scan per customer.

Check the engine-specific syntax and version requirements in PostgreSQL EXISTS reference.

Practice task and worked solution

Return customers who have at least one paid sale. Use EXISTS and keep one result row per customer.

Try the task before reading its worked solution. The supplied rows make the expected answer checkable by hand.

SQL
SELECT c.id,c.name FROM customers AS c
WHERE EXISTS (
  SELECT 1 FROM sales AS s
  WHERE s.customer_id = c.id AND s.status = 'paid'
) ORDER BY c.id;
Output
id | name
1 | Ana
2 | Ben
3 | Cara

The status condition defines the evidence required by the inner query. Each of Ana, Ben and Cara has a paid sale, regardless of any additional pending sales. Dev has no matching evidence. Sale 106 is paid but has no customer link, so it supplies evidence for none of these customers.

Remove the customer correlation temporarily and predict that every customer will pass EXISTS because sales is nonempty. Restore the relationship, then change the inner status from paid to pending: Ana and Cara should remain while Ben and Dev disappear. The selected constant can change from 1 to 99 without changing the truth of the existence test.

SQL EXISTS and NOT EXISTS: Check Related Rows Safely Syntax

EXISTS and NOT EXISTS

SELECT c.id, c.name FROM customers AS c
WHERE NOT EXISTS (
  SELECT 1 FROM sales AS s WHERE s.customer_id = c.id
)
ORDER BY c.id;

SQL EXISTS and NOT EXISTS: Check Related Rows Safely Summary

  • EXISTS (subquery) is true when the subquery returns at least one row; NOT EXISTS is true when it returns none.
  • The subquery must be correlated, for example WHERE s.customer_id = c.id, or it tests the whole table.
  • The selected value inside EXISTS doesn't matter, which is why SELECT 1 is a common convention.
  • EXISTS filters outer rows without duplicating them, unlike a join to a many-side table.
  • NOT EXISTS stays correct when the related column contains NULL, where NOT IN would return nothing.

SQL EXISTS and NOT EXISTS: Check Related Rows Safely Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Customers with no sales

Ana, Ben and Cara each have at least one matching sale, so their NOT EXISTS condition is false. Dev has none, so he remains. The anonymous sale 106 matches no customer and does not change Dev’s result.

SQL
SELECT c.id, c.name FROM customers AS c
WHERE NOT EXISTS (
  SELECT 1 FROM sales AS s WHERE s.customer_id = c.id
)
ORDER BY c.id;
Output
id | name
4  | Dev

Common SQL EXISTS and NOT EXISTS: Check Related Rows Safely Mistakes

Forgetting the correlation condition

Leaving out s.customer_id = c.id makes EXISTS test the whole sales table. Because that table is nonempty, every customer passes even Dev. Selecting s.amount does not fix the missing correlation. The relationship is established by the inner predicate, not by the selected expression.

SQL EXISTS and NOT EXISTS: Check Related Rows Safely in Real Projects

Reconciliation checks for missing records

A reconciliation job can find accounts lacking a required record without producing duplicates from accounts with many valid records. Give the subquery a precise definition of valid evidence, such as paid status or a reporting period. This turns a vague missing-data question into a testable relationship rule.

Define qualifying evidence inside the subquery. For a has-paid-purchase flag, the paid condition belongs beside the customer correlation. Keep the outer SELECT customer-level so several qualifying transactions cannot multiply the displayed customers. Add detail through another query only when the consumer actually needs the individual evidence records.

SQL EXISTS and NOT EXISTS: Check Related Rows Safely FAQ

Is SELECT 1 mandatory?

No. EXISTS normally cares only whether a row is returned, not the chosen value, so SELECT 1, SELECT * and SELECT s.id behave the same. SELECT 1 is a widely used convention that signals this intent to readers.

Does EXISTS remove duplicate outer rows?

No. It filters each outer row, keeping or rejecting it once. If the outer query already contains duplicates, for example from a join, they stay. Start from one customer row if you want one result per customer.

Should I always replace IN with EXISTS?

No. Choose the expression that matches the question and its NULL rules. IN is concise for lists and simple subqueries, and NOT EXISTS is the safe choice for "has no related rows". Neither is universally faster; modern planners often run them the same way.

Is NOT EXISTS the same as a LEFT JOIN with IS NULL?

They answer the same question, rows with no match, and usually return the same result. NOT EXISTS states the intent directly and can't multiply rows; the LEFT JOIN ... WHERE s.id IS NULL form is common too. Test the right-side primary key for NULL, not a column that may legitimately be empty.

Can EXISTS check for several conditions at once?

Yes. Add them to the subquery's WHERE, such as EXISTS (SELECT 1 FROM sales s WHERE s.customer_id = c.id AND s.status = 'paid' AND s.created_on >= '2026-01-01'). Use separate EXISTS tests joined with AND when each condition may be met by a different related row.

SQL EXISTS and NOT EXISTS: Check Related Rows Safely Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choicemedium

Which customer remains in the correlated NOT EXISTS example?

SQL EXISTS and NOT EXISTS: Check Related Rows Safely Quiz

2 questions to check what you learned.

  1. What makes EXISTS true?

    • The subquery returns exactly one row
    • The subquery selects at least one non-NULL value even when no row qualifies
    • The subquery returns at least one qualifying row
  2. What establishes the customer-specific relationship?

    • The inner comparison s.customer_id = c.id
    • The constant SELECT 1 by itself
    • The final outer ORDER BY c.id