SQLQuokka
Start practicing
QuestionsLearnSandboxFeaturesRoadmapFAQ
SQL Concept

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

  • UNION combines results and removes duplicates.
  • UNION ALL keeps duplicates and is faster.
  • Both queries need the same number of columns, in compatible types.
  • Column names come from the first query; one ORDER BY at 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_idcustomer_idorder_dateamount
10112025-01-05120
10212025-01-2080
10322025-01-07200
10412025-02-02150
10532025-02-1090
10622025-02-2560
10732025-03-01130
10812025-03-0340

refunds

refund_idorder_idrefund_dateamount
11022025-01-2530
21062025-03-0260

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;
Result
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;
Result
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;
Result
customer_idnet_revenue
1360
2200
3220

Common mistakes

  • UNION in a ledger. Two refunds of the same amount on the same order would collapse into one with UNION. Use UNION ALL for 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 BY sorts 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.

  1. Camera Buyers Without Memory Cards

    Find customers who bought a camera but never bought a memory card, and count their cameras.

    Medium✓ Solved
  2. Most Connected Members

    Find the member or members with the most friends, counting both sides of each friendship.

    Medium✓ Solved
  3. Income Band Counts

    Count accounts in three fixed income bands, showing bands with no accounts as zero.

    Medium✓ Solved
  4. Top Rater and Top Movie

    Return the most active reviewer and the best-rated movie of February in a single result.

    Medium✓ Solved
  5. People You May Know

    Recommend non-friends who share at least two mutual friends.

    Hard✓ Solved
  6. Monthly Net Revenue by Customer

    Net payments, refunds and chargebacks into the month each money movement happened.

    Hard✓ Solved

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.