MySQL UNION Operator
UNION combines the results of two or more SELECT statements into one result, placing rows one under another and removing duplicates. Use it when similar data lives in different tables: customers and suppliers, this year's and last year's orders, active and archived records.
1. How UNION Works
SELECT columns FROM table1
UNION [DISTINCT]
SELECT columns FROM table2
[UNION SELECT …]
[ORDER BY …] [LIMIT …];
2. A Basic UNION
SELECT city FROM customers
UNION
SELECT city FROM suppliers;
Result: 4 rows
| city |
|---|
| Pune |
| Delhi |
| Goa |
| Kochi |
Delhi and Goa appear in both tables but only once in the result.
3. The Rules
- Every SELECT must return the same number of columns.
- Columns are matched by position, not by name; their types should be compatible.
- Result column names come from the first SELECT.
- Duplicates are judged on whole rows.
SELECT name, city FROM customers
UNION
SELECT company FROM suppliers;
ERROR 1222 (21000): The used SELECT statements have a different number of columns
SELECT name AS contact, city FROM customers
UNION
SELECT company, city FROM suppliers;
Result: 6 rows
| contact | city |
|---|---|
| Arnav | Pune |
| Bela | Delhi |
| Chirag | Goa |
| Goa Spices | Goa |
| Kerala Coir | Kochi |
| Delhi Brass | Delhi |
4. Labelling Where Rows Come From
SELECT 'customer' AS type, name AS contact, email FROM customers
UNION
SELECT 'supplier', company, email FROM suppliers
ORDER BY type, contact;
Result: 6 rows
| type | contact | |
|---|---|---|
| customer | Arnav | arnav@mail.example |
| customer | Bela | bela@mail.example |
| customer | Chirag | chirag@mail.example |
| supplier | Delhi Brass | info@delhibrass.example |
| supplier | Goa Spices | sales@goaspices.example |
| supplier | Kerala Coir | hello@keralacoir.example |
A mailing list from both tables, with a column saying which table each row came from.
5. ORDER BY and LIMIT
One ORDER BY at the end sorts the whole combined result. To sort or limit an individual SELECT, wrap it in parentheses:
(SELECT order_id, amount FROM orders_2025 ORDER BY amount DESC LIMIT 1)
UNION
(SELECT order_id, amount FROM orders_2026 ORDER BY amount DESC LIMIT 1)
ORDER BY amount DESC;
Result: 2 rows
| order_id | amount |
|---|---|
| 23 | 1200 |
| 12 | 750 |
The biggest order of each year.
6. Duplicates Are Removed, Sometimes Surprisingly
Customer 1 placed a ₹500 order in both years. Selecting only customer and amount makes the two rows identical, so UNION keeps one:
SELECT customer_id, amount FROM orders_2025
UNION
SELECT customer_id, amount FROM orders_2026
ORDER BY customer_id;
Result: 5 rows
| customer_id | amount |
|---|---|
| 1 | 500 |
| 1 | 300 |
| 2 | 750 |
| 2 | 1200 |
| 3 | 900 |
SELECT SUM(amount) AS total_wrong FROM (
SELECT customer_id, amount FROM orders_2025
UNION
SELECT customer_id, amount FROM orders_2026
) t;
Result: 1 row
| total_wrong |
|---|
| 3650 |
7. INTERSECT and EXCEPT (MySQL 8.0.31+)
Newer MySQL versions add the other set operations. Cities with both customers and suppliers, and cities with customers only:
SELECT city FROM customers
INTERSECT
SELECT city FROM suppliers;
INTERSECT: in both
| city |
|---|
| Delhi |
| Goa |
SELECT city FROM customers
EXCEPT
SELECT city FROM suppliers;
EXCEPT: in the first only
| city |
|---|
| Pune |
8. UNION vs JOIN
| UNION | JOIN | |
|---|---|---|
| Combines | Rows (vertically) | Columns (horizontally) |
| Needs | Same number of compatible columns | A matching condition (ON) |
| Result width | Same as each SELECT | Columns of both tables |
| Typical use | Same kind of data from several tables | Related data from several tables |
9. Practice Exercises
Exercise 1: List all email addresses from customers and suppliers, alphabetically.
SELECT email FROM customers
UNION
SELECT email FROM suppliers
ORDER BY email;
Result: 6 rows
| arnav@mail.example |
| bela@mail.example |
| chirag@mail.example |
| hello@keralacoir.example |
| info@delhibrass.example |
| sales@goaspices.example |
Exercise 2: Order IDs over ₹600 from either year, with the year.
SELECT 2025 AS yr, order_id, amount FROM orders_2025 WHERE amount > 600
UNION
SELECT 2026, order_id, amount FROM orders_2026 WHERE amount > 600
ORDER BY amount DESC;
Result: 3 rows
| yr | order_id | amount |
|---|---|---|
| 2026 | 23 | 1200 |
| 2026 | 21 | 900 |
| 2025 | 12 | 750 |
10. Summary
- UNION stacks the results of several SELECTs and removes duplicate rows.
- Same number of columns, matched by position; names come from the first SELECT.
- One ORDER BY/LIMIT at the end; use parentheses for per-SELECT ordering.
- Use UNION ALL to keep duplicates (and for speed); INTERSECT/EXCEPT exist since 8.0.31.