EXCEPT

Learn how to use the PostgreSQL EXCEPT operator to return distinct rows from the first query that do not appear in the second query result set.

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

Quick Answer

The PostgreSQL EXCEPT operator returns distinct rows from the first (left) SELECT query that do not appear in the second (right) SELECT query. It is the set-theoretic difference — use it to find records in one data set that are absent from another.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

The EXCEPT operator returns distinct rows from the first SELECT query that do not appear in the second SELECT query. It is the set-theoretic difference — useful for finding records that exist in one data set but are absent from another, such as top-rated films that are not popular, or customers who have not placed an order.

EXCEPT syntax

SELECT select_list FROM A
EXCEPT
SELECT select_list FROM B;

Both queries must return the same number of columns in the same order with compatible data types. Order is significant: A EXCEPT B and B EXCEPT A produce different results. To sort the output:

SELECT select_list FROM A
EXCEPT
SELECT select_list FROM B
ORDER BY sort_expression;

EXCEPT example

Find top-rated films that are not in the most popular list:

SELECT * FROM top_rated_films
EXCEPT
SELECT * FROM most_popular_films;
          title          | release_year
-------------------------+--------------
 The Shawshank Redemption|         1994
 12 Angry Men            |         1957
(2 rows)

The Godfather and The Dark Knight appear in both tables and are excluded from the result.

EXCEPT with ORDER BY

SELECT * FROM top_rated_films
EXCEPT
SELECT * FROM most_popular_films
ORDER BY title;
          title          | release_year
-------------------------+--------------
 12 Angry Men            |         1957
 The Shawshank Redemption|         1994
(2 rows)

EXCEPT vs NOT IN vs anti-join

Three approaches to find rows in A that are absent from B:

-- EXCEPT: compares entire rows, deduplicates, handles NULLs correctly
SELECT * FROM top_rated_films
EXCEPT
SELECT * FROM most_popular_films;

-- NOT EXISTS anti-join: key-based, flexible, NULL-safe
SELECT t.*
FROM top_rated_films t
WHERE NOT EXISTS (
  SELECT 1 FROM most_popular_films m
  WHERE m.title = t.title AND m.release_year = t.release_year
);

-- NOT IN: avoid when subquery may return NULLs
SELECT * FROM top_rated_films
WHERE title NOT IN (SELECT title FROM most_popular_films);

EXCEPT is the most concise for whole-row comparison. The anti-join with NOT EXISTS is more flexible and performs well with indexes on the join key.

Key tips for using EXCEPT

  • EXCEPT is directional: always double-check which query is on the left and which is on the right, as reversing them produces a completely different result.
  • EXCEPT automatically deduplicates the output. Use EXCEPT ALL to preserve duplicates based on count differences between the two result sets.
  • Prefer EXCEPT or NOT EXISTS over NOT IN when the second query may return NULL values — NOT IN with any NULL in the subquery returns zero rows.
  • For large tables, a NOT EXISTS anti-join with an index on the join key often outperforms EXCEPT, which must hash or sort both full result sets.
  • All set operators have equal precedence and are evaluated left to right. Use parentheses to control evaluation order when mixing UNION, INTERSECT, and EXCEPT.

Reference: PostgreSQL documentation — Combining Queries (UNION, INTERSECT, EXCEPT).

Continue in Set Operations: Back to tutorial overview.

Related in this section: UNION · INTERSECT

Frequently Asked Questions

What does the PostgreSQL EXCEPT operator do?

EXCEPT returns rows from the first query that do not appear in the second query. It automatically removes duplicates from the output, similar to UNION and INTERSECT. EXCEPT is directional: "A EXCEPT B" and "B EXCEPT A" produce different results. It is commonly used to find records that exist in one table or query but are missing from another.

What is the difference between EXCEPT and NOT IN?

Both find rows in one set that are absent from another, but they behave differently with NULLs. NOT IN fails silently when the subquery returns any NULL value — the entire condition evaluates to UNKNOWN and no rows match. EXCEPT handles NULLs correctly, treating two NULL values as equal for comparison purposes. EXCEPT also deduplicates the result automatically, while NOT IN does not. Prefer EXCEPT or NOT EXISTS over NOT IN when the subquery may return NULLs.

What are the rules for using EXCEPT in PostgreSQL?

Both SELECT statements must return the same number of columns in the same order, and the data types of corresponding columns must be compatible. ORDER BY can only appear after the final SELECT statement, not inside individual queries. These are the same rules as for UNION and INTERSECT.

Does PostgreSQL support EXCEPT ALL?

Yes. EXCEPT ALL returns rows from the first query that are not matched by the second query, preserving duplicates based on count differences. If a row appears 3 times in the first result and 1 time in the second, EXCEPT ALL returns it 2 times. EXCEPT (without ALL) returns each unmatched distinct row exactly once.

What is the difference between EXCEPT and LEFT JOIN with a NULL check?

A LEFT JOIN with WHERE right.key IS NULL (an anti-join) achieves a similar result to EXCEPT but is more flexible: you can compare on a specific key column rather than all columns, and you can pull additional columns from the left table. EXCEPT compares entire rows for equality, requires matching column structures, and automatically deduplicates. For simple whole-row comparisons, EXCEPT is more concise; for key-based exclusion with mixed column output, use an anti-join.