EXCEPT / MINUS
EXCEPT combines the results of two SELECT queries and returns only the rows that appear in the first result set but do not appear in the second. It is a set-difference operation — think "give me A, minus anything also found in B."
As with UNION and INTERSECT, both queries must return the same number of columns with compatible types, and the operation removes duplicate rows from the output.
Basic syntax
SELECT column1, column2 FROM table_a EXCEPT SELECT column1, column2 FROM table_b;
EXCEPT vs MINUS — same operation, different keyword
The set-difference operation is standard SQL, but Oracle uses a different keyword for historical reasons.
Database | Keyword |
|---|---|
PostgreSQL | EXCEPT |
SQL Server | EXCEPT |
SQLite | EXCEPT |
Oracle | MINUS |
MySQL | Not natively supported in most versions (see below) |
EXCEPT and MINUS behave identically — they just spell the same idea differently depending on the database vendor. If you are writing portable SQL, check which keyword your target database expects.Worked example
A common practical use is comparing two customer lists — for example, finding customers who exist in your CRM export but were never actually imported into the production database.
crm_customers and imported_customers
SELECT customer_email FROM crm_customers EXCEPT SELECT customer_email FROM imported_customers; -- crm_customers: alice@example.com, bob@example.com, carol@example.com -- imported_customers: bob@example.com, carol@example.com -- Result: customers in the CRM export that were never imported -- alice@example.com -- Oracle equivalent SELECT customer_email FROM crm_customers MINUS SELECT customer_email FROM imported_customers;
Rewriting EXCEPT without native support
-- Using NOT EXISTS (usually the best-performing rewrite) SELECT customer_email FROM crm_customers AS a WHERE NOT EXISTS ( SELECT 1 FROM imported_customers AS b WHERE b.customer_email = a.customer_email ); -- Using LEFT JOIN + IS NULL SELECT a.customer_email FROM crm_customers AS a LEFT JOIN imported_customers AS b ON a.customer_email = b.customer_email WHERE b.customer_email IS NULL; -- Using NOT IN — works, but beware of NULLs in the subquery -- (if imported_customers.customer_email can be NULL, NOT IN returns -- no rows at all; NOT EXISTS does not have this problem) SELECT customer_email FROM crm_customers WHERE customer_email NOT IN ( SELECT customer_email FROM imported_customers );
EXCEPT (or MINUS) at all — only very recent MySQL 8.0 releases have added it. For broad MySQL compatibility, use the NOT EXISTS or LEFT JOIN … WHERE … IS NULL pattern shown above instead of relying on the EXCEPT keyword.EXCEPTreturns rows from the first query that are absent from the second.MINUSis Oracle's name for the same operation.Column count and compatible types must match across both queries.
Duplicates are removed automatically from the result.
On databases without native support, use
NOT EXISTSorLEFT JOIN ... IS NULL.