Module 3: Working with Multiple Tables25 min

FULL OUTER JOIN

Progress Tracking

Log in to save this lesson and continue from where you left off.

Log in

Keeping Everything from Both Sides

FULL OUTER JOIN is like running LEFT JOIN and RIGHT JOIN simultaneously. You get all rows from both tables: matches where they exist, NULLs where they don’t.

This join gets used maybe once a month in typical analytics work, but when you need it, nothing else works. The classic use case: data reconciliation between two systems.

SQL
SELECT
  a.customer_id AS system_a_id,
  b.customer_id AS system_b_id,
  a.name AS system_a_name,
  b.name AS system_b_name
FROM system_a_customers a
FULL OUTER JOIN system_b_customers b
  ON a.email = b.email;

When You Actually Need This

FULL OUTER JOIN shines for:

  • Data reconciliation: “Show me what’s in System A but not B, what’s in B but not A, and what matches”
  • Migration validation: comparing old and new databases
  • Gap analysis: finding holes in both datasets
Interview pattern

“Find all unmatched records from both tables.” That’s FULL OUTER JOIN + WHERE both sides have NULL for the primary keys.

Finding Unmatched Records

SQL
SELECT *
FROM table_a a
FULL OUTER JOIN table_b b
  ON a.id = b.ref_id
WHERE a.id IS NULL
  OR b.ref_id IS NULL;
Table: customers
idfirst_namelast_namecityaddressphone_number
8JohnJosephSan Francisco928-386-8164
7JillMichaelAustin813-297-0692
4WilliamDanielDenver813-368-1200
5HenryJacksonMiami808-601-7513
13EmmaIsaacMiami808-690-5201
Table: orders
idcust_idorder_dateorder_detailstotal_order_cost
132019-03-04Coat100
232019-03-01Shoes80
332019-03-07Skirt30
472019-02-01Coat25
572019-03-10Shoes80
1
Find Unmatched Records

Full outer join customers and orders, then filter to rows that only exist on one side.

Tables: customers, orders

This gives you a clean list of everything missing on either side.

MySQL Doesn’t Support This

MySQL has no FULL OUTER JOIN

You have to simulate it with UNION.

The workaround isn’t pretty, but it works:

SQL
-- MySQL: Simulate FULL OUTER JOIN
SELECT
  a.id,
  a.name,
  b.id AS b_id,
  b.name AS b_name
FROM table_a a
LEFT JOIN table_b b
  ON a.id = b.id

UNION

SELECT
  a.id,
  a.name,
  b.id AS b_id,
  b.name AS b_name
FROM table_a a
RIGHT JOIN table_b b
  ON a.id = b.id;

UNION removes the duplicates (rows that match appear in both queries). This is correct, but can be slow.

The Faster Version

For large tables, the UNION dedup overhead can be slow. The optimization: use UNION ALL instead, but add WHERE a.id IS NULL to the second query so it only returns rows from B that have no match in A. Combined with the first query (all of A’s perspective), you get the full result without duplicate checking.

PostgreSQL, SQL Server, and Oracle

If you’re on PostgreSQL, SQL Server, or Oracle, just use FULL OUTER JOIN directly. The MySQL workaround is only necessary on MySQL.

Watch Out For: Same Entity, Different IDs

FULL OUTER JOIN can mislead you when reconciling systems that use different identifiers for the same entity:

  • Customer ‘John Smith’ might be ID 123 in System A and ID 456 in System B
  • Name variations (‘John Smith’ vs ‘J. Smith’) won’t match
  • Typos and data quality issues cause false “no match” results

For serious reconciliation work, you often need fuzzy matching or a shared identifier. FULL OUTER JOIN is just the mechanical part.

Key Takeaways

  • FULL OUTER JOIN returns all rows from both tables
  • Use it for data reconciliation and finding gaps in both directions
  • MySQL requires a UNION workaround (use UNION ALL + WHERE IS NULL for performance)

What’s Next

All the joins we’ve covered require an ON clause to define matching logic. But what if you want every possible combination? That’s CROSS JOIN, and while it’s dangerous, it has legitimate uses.