FULL OUTER JOIN
Keep all rows from both tables — reconciliation reports, symmetric difference queries, gap analysis across two data sources, and every pattern where neither side can be dropped
// Part 01
What FULL OUTER JOIN Does
INNER JOIN keeps only matched rows. LEFT JOIN keeps all left rows plus matched right rows. RIGHT JOIN keeps all right rows plus matched left rows. FULL OUTER JOIN keeps everything — all rows from both tables, whether or not they match.
For rows that match, both sides are populated normally. For left rows with no right match, right-side columns are NULL. For right rows with no left match, left-side columns are NULL. No row from either table is ever discarded.
| JOIN type | Matched rows | Left-only rows | Right-only rows | Use when |
|---|---|---|---|---|
| INNER JOIN | ✅ included | ❌ excluded | ❌ excluded | Only matched data matters |
| LEFT JOIN | ✅ included | ✅ included | ❌ excluded | All left rows, optional right data |
| RIGHT JOIN | ✅ included | ❌ excluded | ✅ included | All right rows, optional left data |
| FULL OUTER JOIN | ✅ included | ✅ included | ✅ included | All rows from both, gaps visible |
// Part 02
Basic Syntax and Behaviour
// Part 03
Reconciliation — The Primary Use Case
The most common production use of FULL OUTER JOIN is reconciliation — comparing two data sources that should contain the same records and identifying discrepancies. One source may have records the other is missing, both may have records the other lacks, and some records exist in both but with different values.
Reconciling two versions of the same data
Reconciling product catalogue vs sales data
// Part 04
Symmetric Difference — Rows Unique to Each Side
The symmetric difference is all rows that appear in exactly one of the two tables — neither in both. This is FULL OUTER JOIN with a WHERE that keeps only the unmatched rows from both sides simultaneously.
// Part 05
COALESCE with FULL OUTER JOIN — Merging Both Sides
When both sides may have values for the same column (or different columns representing the same concept), COALESCE merges them into a single non-NULL value. This is essential for producing clean output from a FULL OUTER JOIN where either side may be NULL.
Comparing two monthly snapshots
// Part 06
FULL OUTER JOIN for Gap Analysis
Gap analysis uses FULL OUTER JOIN to identify where data is missing in a time series or expected dimension. If you expect data for every store every month but some combinations are absent, FULL OUTER JOIN against a complete reference set reveals the gaps.
Generate the complete expected grid, then find gaps
// Part 07
Simulating FULL OUTER JOIN with UNION ALL
MySQL does not support FULL OUTER JOIN syntax. The workaround is to combine a LEFT JOIN and a RIGHT JOIN (or two LEFT JOINs) using UNION ALL — this produces an equivalent result.
// Part 08
When to Use FULL OUTER JOIN — Decision Guide
FULL OUTER JOIN is specialised and less common than INNER or LEFT JOIN. Reaching for it reflexively is usually wrong — most business questions are answered by INNER or LEFT JOIN. Use FULL OUTER JOIN specifically when the business question genuinely requires seeing gaps on both sides simultaneously.
// Part 09
What This Looks Like at Work
You are a data engineer at HDFC Bank's digital banking division. Every night, the transaction processing system writes payment records to two databases: the primary OLTP database and an independent audit database. The compliance team runs a daily reconciliation to ensure both databases agree. Any transaction present in one but absent in the other — or present in both but with different amounts — must be flagged for investigation.
🎯 Pro Tip
FULL OUTER JOIN is the correct tool for any reconciliation job. The reason: you do not know in advance which records are missing from which side — that is exactly what you are trying to discover. LEFT JOIN would miss records in the right-side-only bucket. RIGHT JOIN would miss records in the left-side-only bucket. Only FULL OUTER JOIN guarantees that all discrepancies on both sides are visible in one query.
// Part 10
Interview Prep — 5 Questions With Complete Answers
FULL OUTER JOIN returns all rows from both tables. For rows where the join condition is satisfied, columns from both sides are populated. For left-table rows with no match in the right table, right-side columns are NULL. For right-table rows with no match in the left table, left-side columns are NULL. No row from either table is ever excluded — it is the union of LEFT JOIN and RIGHT JOIN.
The defining use case is reconciliation — comparing two data sources that should contain the same records and surfacing discrepancies on both sides simultaneously. A payment reconciliation between a primary OLTP database and an audit database needs to find: transactions in the primary but missing in audit, transactions in audit but missing in the primary, and transactions in both but with different amounts. Only FULL OUTER JOIN reveals all three categories in one query.
Other legitimate uses: month-over-month comparison where some stores only appear in one month (FULL OUTER JOIN ensures both months' data appears even when a store has no activity in one of them), gap analysis against a complete reference grid, symmetric difference queries (rows unique to either side), and data migration validation where records from an old system are being compared against a new system. FULL OUTER JOIN is specialised — most analytical questions are answered by INNER or LEFT JOIN. Reach for FULL OUTER JOIN specifically when gaps on both sides simultaneously need to be visible.
MySQL does not support FULL OUTER JOIN syntax (or supported it only very limitedly in older versions). The standard workaround is to combine a LEFT JOIN and the right-only portion of a RIGHT JOIN using UNION ALL.
The pattern: Part 1 — LEFT JOIN gives all left rows and matched right rows (left-only + matched). Part 2 — RIGHT JOIN with WHERE left.key IS NULL gives only the right rows that had no left match (right-only). UNION ALL combines both parts. Together: left-only + matched + right-only = FULL OUTER JOIN.
The critical detail: use UNION ALL not UNION. UNION deduplicates rows, which can incorrectly remove legitimate duplicate rows. UNION ALL preserves all rows from both parts — the two parts are designed to be disjoint (Part 1 has no right-only rows, Part 2 has only right-only rows) so deduplication is unnecessary and harmful. The resulting query is slightly more verbose than FULL OUTER JOIN but produces identical results and works on all MySQL versions. In PostgreSQL, DuckDB, SQL Server, and Oracle, FULL OUTER JOIN is directly supported and the UNION ALL workaround is unnecessary.
The symmetric difference of two sets is the collection of elements that appear in exactly one set but not both — the elements unique to each side. In relational terms, it is all rows from table A that have no match in table B, plus all rows from table B that have no match in table A. Rows that appear in both are excluded.
The SQL pattern is FULL OUTER JOIN with a WHERE that keeps only the unmatched rows: SELECT COALESCE(a.key, b.key) FROM table_a AS a FULL OUTER JOIN table_b AS b ON a.key = b.key WHERE a.key IS NULL OR b.key IS NULL. The WHERE condition keeps rows where either side is NULL — which are exactly the rows with no match on one side. Rows where both sides are non-NULL are the matched rows — excluded by the OR IS NULL filter.
Use cases for symmetric difference in production: finding records that exist in a staging table but not in the production table after a migration (and vice versa), identifying customers who are in a CRM but not in the transaction database (and vice versa), finding discrepancies between two snapshots of the same data taken at different times. The symmetric difference gives you a complete picture of all anomalies in one query — both "in A but not B" and "in B but not A" simultaneously. In set theory terms, it is A XOR B or (A ∪ B) − (A ∩ B).
The pattern: aggregate each period into a CTE separately, then FULL OUTER JOIN the two CTEs on the shared dimension key (like store_id or product_id). COALESCE handles the cases where an entity appears in one period but not the other.
Example for store revenue Jan vs Feb: WITH jan AS (SELECT store_id, SUM(revenue) AS revenue FROM orders WHERE month = 1 GROUP BY store_id), feb AS (SELECT store_id, SUM(revenue) AS revenue FROM orders WHERE month = 2 GROUP BY store_id) SELECT COALESCE(j.store_id, f.store_id) AS store_id, COALESCE(j.revenue, 0) AS jan, COALESCE(f.revenue, 0) AS feb, COALESCE(f.revenue, 0) - COALESCE(j.revenue, 0) AS change FROM jan AS j FULL OUTER JOIN feb AS f ON j.store_id = f.store_id.
The COALESCE(j.store_id, f.store_id) for the key column handles both cases: if the store only appears in January, j.store_id is populated and f.store_id is NULL — COALESCE returns j.store_id. If the store only appears in February, j.store_id is NULL — COALESCE falls back to f.store_id. COALESCE(j.revenue, 0) and COALESCE(f.revenue, 0) convert NULLs to 0 so the arithmetic change calculation works correctly for new or disappearing stores. A CASE WHEN can classify each store as 'New', 'Churned', 'Growth', 'Decline', or 'Flat' based on which side is NULL and whether revenue went up or down.
FULL OUTER JOIN and UNION ALL both combine data from two sources, but they operate differently and produce different result structures. FULL OUTER JOIN is a horizontal combination — it combines columns from two tables side by side based on a join condition, producing a result with columns from both tables. UNION ALL is a vertical combination — it stacks rows from two queries on top of each other, producing a result with the same columns as each query (which must match in count and compatible types).
FULL OUTER JOIN requires a join condition (ON clause) that defines how rows from both sides relate. Matched rows appear with all columns populated; unmatched rows appear with NULLs on the missing side. UNION ALL has no join condition — it simply concatenates rows. Both queries must return the same number of columns in the same order. It produces duplicate rows if they exist in both queries (unlike UNION which deduplicates).
They serve fundamentally different purposes: FULL OUTER JOIN is for matching related data between two tables with a defined relationship and seeing where gaps exist. UNION ALL is for combining the same type of data from different sources or time periods into one result set. The FULL OUTER JOIN simulation for MySQL uses UNION ALL internally — it combines a LEFT JOIN result with a right-only result to reproduce FULL OUTER JOIN's output. But the UNION ALL in the simulation is an implementation detail, not a conceptual equivalent. FULL OUTER JOIN is structurally a JOIN (horizontal); UNION ALL is structurally a set operation (vertical).
// Part 11
Errors You Will Hit — And Exactly Why They Happen
🎯 Key Takeaways
- ✓FULL OUTER JOIN keeps all rows from both tables. Matched rows have both sides populated. Unmatched rows have NULLs on the missing side. No row from either table is ever discarded.
- ✓The primary use case is reconciliation — comparing two data sources to find records missing from either side and value discrepancies in records that appear in both.
- ✓Symmetric difference: FULL OUTER JOIN + WHERE left.key IS NULL OR right.key IS NULL — keeps only rows with no match on one side, excluding matched rows.
- ✓COALESCE(left.key, right.key) produces a unified key column that is non-NULL regardless of which side has the data. Always put the preferred side first.
- ✓Month-over-month and period comparison: FULL OUTER JOIN two aggregated CTEs on the dimension key. COALESCE converts NULLs to 0 for arithmetic comparisons.
- ✓Gap analysis: CROSS JOIN a complete reference grid with actual data, then FULL OUTER JOIN to find which expected combinations are missing in actuals.
- ✓MySQL does not support FULL OUTER JOIN natively. Simulate with LEFT JOIN UNION ALL (RIGHT JOIN WHERE left.key IS NULL). Use UNION ALL not UNION.
- ✓Use FULL OUTER JOIN selectively — most questions need INNER JOIN or LEFT JOIN. Reach for FULL OUTER JOIN only when gaps on both sides simultaneously need to be visible.
- ✓IS DISTINCT FROM is safer than <> for reconciliation value comparisons — it handles NULLs correctly. NULL IS DISTINCT FROM NULL = FALSE (they match); NULL IS DISTINCT FROM 5 = TRUE (they differ).
- ✓FULL OUTER JOIN + CASE WHEN gives a complete reconciliation report in one query: matched rows, left-only rows, right-only rows, and value mismatches all labelled and sortable.
What comes next
In Module 34, you learn SELF JOIN — joining a table to itself, the technique for hierarchical queries, adjacency list traversal, and comparing rows within the same table.
Module 34 → SELF JOINDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.