SQL UNION vs UNION ALL: Combining Query Results
UNION stacks the rows of two queries on top of each other. UNION removes duplicate rows; UNION ALL keeps every row. Choosing the wrong one silently changes counts and totals.
Examples run in MySQL 8 and PostgreSQL · Updated
In short
UNIONcombines results and removes duplicates.UNION ALLkeeps duplicates and is faster.- Both queries need the same number of columns, in compatible types.
- Column names come from the first query; one
ORDER BYat the end sorts everything.
The rules
Each part of a union must return the same number of columns, matched by position, not by name. The result takes its column names from the first SELECT, and a single ORDER BY after the last part sorts the combined rows.
Use UNION ALL whenever duplicates are meaningful, such as transactions in a ledger. Use UNION for "distinct values from either list".
The example tables
Every example on this page uses these tables. They are small on purpose, so you can check each result by eye.
orders
| order_id | customer_id | order_date | amount |
|---|---|---|---|
| 101 | 1 | 2025-01-05 | 120 |
| 102 | 1 | 2025-01-20 | 80 |
| 103 | 2 | 2025-01-07 | 200 |
| 104 | 1 | 2025-02-02 | 150 |
| 105 | 3 | 2025-02-10 | 90 |
| 106 | 2 | 2025-02-25 | 60 |
| 107 | 3 | 2025-03-01 | 130 |
| 108 | 1 | 2025-03-03 | 40 |
refunds
| refund_id | order_id | refund_date | amount |
|---|---|---|---|
| 1 | 102 | 2025-01-25 | 30 |
| 2 | 106 | 2025-03-02 | 60 |
Examples
UNION removes duplicates
Customers who ordered in January (1 and 2) or in March (3 and 1). Customer 1 appears once.
SELECT customer_id FROM orders WHERE order_date < '2025-02-01'
UNION
SELECT customer_id FROM orders WHERE order_date >= '2025-03-01'
ORDER BY customer_id;| customer_id |
|---|
| 1 |
| 2 |
| 3 |
UNION ALL keeps every row
The same two queries keep all five rows, including customer 1 three times.
SELECT customer_id FROM orders WHERE order_date < '2025-02-01'
UNION ALL
SELECT customer_id FROM orders WHERE order_date >= '2025-03-01'
ORDER BY customer_id;| customer_id |
|---|
| 1 |
| 1 |
| 1 |
| 2 |
| 3 |
A ledger from two tables
Orders add money and refunds take it away. Stack both with UNION ALL, then total per customer.
WITH ledger AS (
SELECT order_id, amount FROM orders
UNION ALL
SELECT order_id, -amount FROM refunds
)
SELECT o.customer_id, SUM(l.amount) AS net_revenue
FROM ledger l
JOIN orders o ON o.order_id = l.order_id
GROUP BY o.customer_id
ORDER BY o.customer_id;| customer_id | net_revenue |
|---|---|
| 1 | 360 |
| 2 | 200 |
| 3 | 220 |
Common mistakes
- UNION in a ledger. Two refunds of the same amount on the same order would collapse into one with
UNION. UseUNION ALLfor transactions. - Mismatched columns. Different column counts are an error; columns in a different order are not, and quietly mix up data. Line columns up by position.
- ORDER BY in each part. Only the final
ORDER BYsorts the result. Ordering inside a part needs parentheses and usually has no effect.
Practise UNION vs UNION ALL
Interview questions that use this concept, easiest first. Each one has the step-by-step visualizer and answer checking.
Camera Buyers Without Memory Cards
Find customers who bought a camera but never bought a memory card, and count their cameras.
Most Connected Members
Find the member or members with the most friends, counting both sides of each friendship.
Income Band Counts
Count accounts in three fixed income bands, showing bands with no accounts as zero.
Top Rater and Top Movie
Return the most active reviewer and the best-rated movie of February in a single result.
People You May Know
Recommend non-friends who share at least two mutual friends.
Monthly Net Revenue by Customer
Net payments, refunds and chargebacks into the month each money movement happened.
Want a full set? Work through the Subqueries & CTEs study plan.
Questions people ask
- Is UNION slower than UNION ALL?
- Usually, yes: removing duplicates needs an extra sort or hash step. Prefer UNION ALL when duplicates are impossible or wanted.
- What about INTERSECT and EXCEPT?
- PostgreSQL supports both, and MySQL supports them since 8.0.31. They keep rows found in both queries, or in the first but not the second.