EXISTS Operator

Learn how to use the PostgreSQL EXISTS operator to test whether a subquery returns any rows, enabling efficient presence checks and correlated filtering.

5 min read · Last updated: March 2026 · Back to overview

Quick Answer

The PostgreSQL EXISTS operator returns true if its subquery returns at least one row, and false if the subquery returns no rows. It is commonly used with correlated subqueries in WHERE clauses to check whether related rows exist.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

The PostgreSQL EXISTS operator is a boolean operator that checks whether a subquery returns any rows. It returns true if the subquery produces at least one row, and false if it produces none. EXISTS is evaluated purely on row presence — column values are irrelevant.

EXISTS syntax

SELECT select_list
FROM table1
WHERE EXISTS (
  SELECT 1
  FROM table2
  WHERE condition
);

The convention is to write SELECT 1 inside EXISTS because the column values are never used. The subquery is typically a correlated subquery that references the outer query's current row.

Basic EXISTS check

This query checks whether any payment with an amount of zero exists in the payment table:

SELECT EXISTS (
  SELECT 1
  FROM payment
  WHERE amount = 0
);

Output:

exists
--------
t
(1 row)

The result is t (true) because at least one row matches the condition.

Using EXISTS to filter rows

This query finds customers who have made at least one payment greater than 11:

SELECT first_name, last_name
FROM customer c
WHERE EXISTS (
  SELECT 1
  FROM payment p
  WHERE p.customer_id = c.customer_id
    AND amount > 11
)
ORDER BY first_name, last_name;

Output:

first_name | last_name
------------+-----------
Karen      | Jackson
Kent       | Arsenault
Nicholas   | Barfield
Rosemary   | Schmidt
Tanya      | Gilbert
Terrance   | Roush
Vanessa    | Sims
Victoria   | Gibson
(8 rows)

For each customer in the outer query, the correlated subquery checks whether a matching payment exists. EXISTS stops checking as soon as it finds the first qualifying payment row.

Using NOT EXISTS to find absent relationships

NOT EXISTS finds customers who have never made a payment greater than 11:

SELECT first_name, last_name
FROM customer c
WHERE NOT EXISTS (
  SELECT 1
  FROM payment p
  WHERE p.customer_id = c.customer_id
    AND amount > 11
)
ORDER BY first_name, last_name;

This returns the complement — every customer not found in the EXISTS query above. NOT EXISTS is the standard way to implement "anti-join" logic in SQL.

EXISTS and NULL behavior

If the subquery returns NULL, EXISTS still returns true because a row was produced:

-- Returns every customer because the subquery always produces a row (NULL)
SELECT first_name, last_name
FROM customer
WHERE EXISTS (SELECT NULL)
ORDER BY first_name, last_name;

This confirms that EXISTS evaluates row presence only — not the truthiness of any returned value.

Tips for using EXISTS effectively

  • Use SELECT 1 inside EXISTS as a clear signal that only row existence matters, not column data.
  • EXISTS short-circuits after finding the first matching row, which can make it faster than IN when the subquery matches many rows.
  • Use NOT EXISTS instead of NOT IN when the subquery column may contain NULLs — NOT IN with any NULL in the set returns NULL (unknown) for every row, silently eliminating all results.
  • Index the correlated join columns (e.g., payment.customer_id) to make the inner lookup fast for each outer row.

Reference: PostgreSQL documentation — Subquery Expressions (EXISTS).

Continue in Subquery: Back to tutorial overview.

Related in this section: Subquery · Correlated Subquery · ANY Operator

Frequently Asked Questions

What does the EXISTS operator do in PostgreSQL?

EXISTS is a boolean operator that evaluates a subquery and returns true if the subquery produces at least one row. It returns false if the subquery returns no rows. The column values in the subquery result do not matter — only whether any row exists.

Why is SELECT 1 commonly used inside EXISTS?

Because EXISTS only cares about row existence, not column values, writing SELECT 1 (or SELECT NULL) instead of actual column names makes the intent explicit and avoids fetching unnecessary data. The query optimizer treats all of these identically, but SELECT 1 is the conventional style.

What is NOT EXISTS used for?

NOT EXISTS returns true when the subquery returns zero rows, and false when it returns at least one row. It is commonly used to find rows in one table that have no corresponding rows in another — for example, customers who have never made a payment, or products never ordered.

What happens when the EXISTS subquery returns NULL?

If the subquery returns NULL (for example, SELECT NULL), EXISTS still returns true because a row was returned — even though its value is NULL. EXISTS cares only about row presence, not the value of any column.

Is EXISTS faster than IN for large datasets?

EXISTS can be more efficient than IN when the subquery matches a large number of rows, because EXISTS short-circuits after finding the first match. IN typically collects all matching values before comparing. However, modern PostgreSQL query planning often produces equivalent plans. Use EXPLAIN ANALYZE to verify in your specific case.