Chapter 12 — Set Operations: UNION & UNION ALL
1. What is it?
In relational algebra, set operators combine the results of two or more independent SELECT queries into a single unified result set. While a JOIN combines tables horizontally (appending columns from one table to columns of another), a Set Operation combines queries vertically (stacking rows from one query on top of rows from another).
SQL provides two primary set combination operators:
UNION: Combines the output of two or more queries, automatically performing an implicit deduplication phase. Identical rows appearing in multiple queries are condensed into a single unique row.UNION ALL: Combines the output of two or more queries without deduplication. All matching rows from all queries are preserved, including duplicates.
Because UNION requires the database engine to sort the combined dataset in memory or build a temporary hash table to identify and eliminate duplicate rows, it incurs significant computational overhead. Conversely, UNION ALL simply streams the rows from each query sequentially, making it significantly faster.
2. Strict Schema Compatibility Rules
To combine queries using UNION or UNION ALL, the participating queries must satisfy three strict relational compatibility requirements:
- Identical Column Count: Every
SELECTstatement in the compound query must project the exact same number of columns. - Compatible Data Types: Corresponding columns in each query (Column 1 to Column 1, Column 2 to Column 2) must have compatible or implicitly convertible data types. For example, a
VARCHARcolumn cannot be matched with aDATEcolumn unless explicitly cast. - Column Naming Precedence: The column names, data types, and aliases of the final output are determined exclusively by the first
SELECTquery in the chain.
flowchart TD
subgraph U ["UNION (Deduplicated)"]
direction TB
Q1["Query A (Rows: 1, 2, 3)"] --- O1["Engine Sort & Deduplicate"]
Q2["Query B (Rows: 2, 3, 4)"] --- O1
O1 --> R1["Result: Rows 1, 2, 3, 4"]
end
subgraph UA ["UNION ALL (Fast Concatenation)"]
direction TB
Q3["Query A (Rows: 1, 2, 3)"] --- O2["Direct Stream"]
Q4["Query B (Rows: 2, 3, 4)"] --- O2
O2 --> R2["Result: Rows 1, 2, 3, 2, 3, 4"]
end3. Syntax
-- Standard UNION (Implicit Deduplication)
SELECT column1, column2, ...
FROM table1
WHERE condition1
UNION
SELECT column1, column2, ...
FROM table2
WHERE condition2;
-- High-Performance UNION ALL (Preserves Duplicates)
SELECT column1, column2, ...
FROM table1
UNION ALL
SELECT column1, column2, ...
FROM table2;
-- Global Ordering and Pagination of a Compound Query
(SELECT id, name, created_at FROM table1)
UNION ALL
(SELECT id, name, created_at FROM table2)
ORDER BY created_at DESC
LIMIT 20;4. Basic Example
Demonstrating UNION vs UNION ALL across geographic locations:
USE sql_mastery;
-- UNION: Distinct list of cities where we have either customers OR suppliers
SELECT city, country, 'Customer Base' AS entity_source
FROM customers
WHERE country = 'USA'
UNION
SELECT city, country, 'Supplier Base' AS entity_source
FROM suppliers
WHERE country = 'USA';
-- Compare without the entity_source column:
-- UNION removes duplicate cities (e.g. Seattle)
SELECT city, country FROM customers WHERE country = 'USA'
UNION
SELECT city, country FROM suppliers WHERE country = 'USA';
-- UNION ALL preserves both occurrences of duplicate cities
SELECT city, country FROM customers WHERE country = 'USA'
UNION ALL
SELECT city, country FROM suppliers WHERE country = 'USA';5. Real-World Example
The enterprise security and audit office requires an aggregated Corporate Directory & Activity Feed that merges:
- Internal employees (
employeestable) with contact details, department, and a'Staff'role. - External supplier contacts (
supplierstable) with contact details, company name, and a'Vendor'role. - Customer contacts (
customerstable) with city, country, and a'Customer'role. - The final unified directory must be ordered alphabetically by contact name.
USE sql_mastery;
(
SELECT
CONCAT(first_name, ' ', last_name) AS full_name,
email AS contact_email,
phone AS contact_phone,
'Internal Staff' AS entity_type,
d.department_name AS affiliation
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
)
UNION ALL
(
SELECT
contact_name AS full_name,
contact_email AS contact_email,
contact_phone AS contact_phone,
'External Vendor' AS entity_type,
supplier_name AS affiliation
FROM suppliers
)
UNION ALL
(
SELECT
CONCAT(first_name, ' ', last_name) AS full_name,
email AS contact_email,
phone AS contact_phone,
'Registered Customer' AS entity_type,
CONCAT(city, ', ', country) AS affiliation
FROM customers
)
ORDER BY full_name ASC;6. Step-by-Step Explanation
- First Query (
employees):- Extracts the full name, email, phone, and joins
departmentsto label internal company personnel. - Defines the output column schema:
full_name,contact_email,contact_phone,entity_type,affiliation.
- Extracts the full name, email, phone, and joins
- Second Query (
suppliers):- Maps supplier contact metadata to the exact same 5-column positional structure.
supplier_nameis positioned to populate theaffiliationcolumn.
- Maps supplier contact metadata to the exact same 5-column positional structure.
- Third Query (
customers):- Maps customer personal details into the exact same 5-column layout.
UNION ALLProcessing:- The database engine skips in-memory deduplication sorting and streams the tuples directly from each table into a single intermediate result set.
- Global
ORDER BY full_name ASC:- The entire combined result set across all three source tables is sorted alphabetically by
full_name.
- The entire combined result set across all three source tables is sorted alphabetically by
7. Expected Result
Partial output of the Unified Corporate Directory:
+-------------------+----------------------------+---------------+---------------------+------------------------+
| full_name | contact_email | contact_phone | entity_type | affiliation |
+-------------------+----------------------------+---------------+---------------------+------------------------+
| Aisha Khan | aisha.khan@domain.in | 555-0305 | Registered Customer | Bengaluru, India |
| Alex Morgan | alex.morgan@company.com | 555-0100 | Internal Staff | Engineering |
| Astrid Lind | lind@nordictm.se | 555-0205 | External Vendor | Nordic Timber & Metal |
| Carlos Mendoza | carlos.mendoza@company.com | 555-0108 | Internal Staff | Supply Chain |
| Chloe Dubois | chloe.dubois@orange.fr | 555-0309 | Registered Customer | Lyon, France |
| David Kim | david.kim@company.com | 555-0104 | Internal Staff | Data & Analytics |
| Elena Rostova | elena.rostova@company.com | 555-0105 | Internal Staff | Sales & Marketing |
| Emily Watson | emily.watson@gmail.com | 555-0301 | Registered Customer | San Francisco, USA |
| Greta Weber | weber@eurosmart.de | 555-0203 | External Vendor | EuroSmart Manufacturing|
+-------------------+----------------------------+---------------+---------------------+------------------------+8. Common Mistakes
- Column Count Mismatch:
- Mistake:sql
SELECT employee_id, first_name, email FROM employees UNION SELECT customer_id, first_name FROM customers; -- ONLY 2 COLUMNS! - Error:
ERROR 1222 (21000): The used SELECT statements have a different number of columns. - Rule: All participating queries must project the exact same number of columns.
- Mistake:
- Defaulting to
UNIONInstead ofUNION ALL:- Mistake: Writing
UNIONwhen you know the two datasets cannot overlap (e.g., combining data fromcustomersandsuppliers). - Consequence: The engine builds an expensive temporary table and performs an unnecessary sort to look for duplicate rows that can never exist, degrading query throughput.
- Mistake: Writing
- Placing
ORDER BYInside Individual Queries Without Parentheses:- Writing:sqlCauses a syntax error. If you need local ordering or limits before merging, each query must be enclosed in parentheses:
SELECT name FROM tableA ORDER BY name UNION SELECT name FROM tableB;sql(SELECT name FROM tableA ORDER BY name LIMIT 5) UNION ALL (SELECT name FROM tableB ORDER BY name LIMIT 5);
- Writing:
- Expecting Column Names from Later Queries to Matter:
- If Query 1 aliases a column as
account_idand Query 2 aliases the same positional column ascustomer_number, the output column will be namedaccount_id. Always verify column aliases in the firstSELECTstatement.
- If Query 1 aliases a column as
9. Best Practices
- Default to
UNION ALLUnless Deduplication Is Explicitly Required:- Always write
UNION ALLby default. Only useUNIONwhen duplicate rows are expected and business requirements explicitly mandate their removal.
- Always write
- Always Align Column Data Types Positively:
- Avoid relying on implicit type coercion (e.g., merging an integer column with a string column). Use explicit
CAST()functions to harmonize types:sqlSELECT CAST(employee_id AS CHAR(20)) FROM employees UNION ALL SELECT reference_code FROM external_partners;
- Avoid relying on implicit type coercion (e.g., merging an integer column with a string column). Use explicit
- Use Static Literal Tags to Identify Row Provenance:
- When combining disparate tables, include a constant string literal (e.g.,
'Order','Refund','Adjustment') to allow client code to distinguish row origin.
- When combining disparate tables, include a constant string literal (e.g.,
10. Practice Questions
Easy
- Write a query using
UNIONto combine thecitycolumn from thecustomerstable with thelocationcolumn from thedepartmentstable into a single list. - Write a query using
UNION ALLto list all email addresses across bothemployeesandcustomers. - Explain the difference in row counts between your answers to Question 1 and Question 2.
Medium
- Write a query that combines all products with
unit_price > 500and all products withstock_quantity < 20, usingUNIONto ensure products meeting both criteria are only listed once. - Write a query that generates a unified ledger of financial movements:
- Positive order values from
orderswherestatus = 'Delivered'(tagged as'REVENUE') - Negative shipping costs from
orderswhereshipping_fee > 0(tagged as'EXPENSE') - Order the unified ledger by date descending.
- Positive order values from
- Write a query combining the names of active customers and inactive customers into two distinct partitions, with each row labeled with their respective status.
Difficult
- Write a query that emulates a
FULL OUTER JOINbetweendepartmentsandemployeesusingLEFT JOIN,RIGHT JOIN, andUNION. Verify that departments without employees and employees without departments are both present. - Construct a query that takes the top 2 highest-paid employees and merges them with the top 2 lowest-paid employees using
UNION ALL, sorted overall by salary descending. (Hint: Utilize parenthesized subqueries with individualLIMITclauses).
11. Interview Questions
Q1: What is the mechanical difference between UNION and UNION ALL in terms of execution mechanics and performance?
Answer:
UNIONconcatenates the result sets of two queries and then performs an implicit deduplication step. To do this, the database engine must spill the combined rows into an in-memory or on-disk temporary table, sort the records across all projected columns (or construct a hash set), and eliminate duplicates. This consumes significant CPU, memory, and I/O.UNION ALLperforms a pure vertical concatenation. The engine streams the rows produced by Query 1 directly to the client or parent pipeline, followed immediately by Query 2, performing zero sorting, hashing, or comparisons. Consequently,UNION ALLis orders of magnitude faster and should always be preferred when records are known to be distinct or when duplicates are acceptable.
Q2: What are the three relational rules that two queries must satisfy to be combined using a Set Operator?
Answer:
- Identical Degree (Column Count): Both
SELECTqueries must project the exact same number of columns. - Type Compatibility: The data types of columns in corresponding positions must be identical or implicitly convertible by the database engine (e.g., an integer and a float, but not a date and a binary blob).
- Order of Evaluation: The column names, aliases, and character collations of the final output set are dictated by the first
SELECTstatement in the union chain.
Q3: How can you apply an ORDER BY to an entire compound query versus applying an ORDER BY to an individual branch of a UNION?
Answer:
- To apply an
ORDER BYacross the entire compound result set, place a singleORDER BYclause at the very end of the final query without parentheses. It evaluates on the combined output. - To apply an
ORDER BY(typically combined with aLIMIT) to individual branches, each branch query must be enclosed in its own parentheses:sql(SELECT * FROM table1 ORDER BY score DESC LIMIT 5) UNION ALL (SELECT * FROM table2 ORDER BY score DESC LIMIT 5) ORDER BY score DESC;
12. Quick Revision
UNIONstacks query results vertically and eliminates duplicate rows (involves sorting overhead).UNION ALLstacks query results vertically without removing duplicates (maximum performance).- All combined queries must have the same number of columns with compatible data types.
- The first query sets the column names and aliases for the final result set.
- Wrap individual queries in parentheses when using per-branch
LIMITorORDER BY.