SQLab Hub
Lesson 16 · FULL OUTER JOIN
Lesson 16 · Querying

SQL FULL OUTER JOIN: Syntax, Examples & Practice

SQL FULL OUTER JOIN returns every row from both tables. Rows that match are combined; rows with no match on either side are still returned, with NULL in the other table's columns. FULL JOIN is the same thing.

⟗ Every row of both tables ▶ Runnable Playground examples 🔍 Find mismatches 🐬 MySQL workaround 🧪 7 practice exercises 📝 10-question quiz

What Is a FULL OUTER JOIN in SQL?

Take two lists from the Playground database: customers who have placed an order (buyers) and customers who have written a review (reviewers). Some people are on both lists, some on only one. A FULL OUTER JOIN lines the lists up and keeps everybody:

SQLite · runs in the Playground
WITH buyers AS (
  SELECT DISTINCT user_id FROM orders
), reviewers AS (
  SELECT DISTINCT user_id FROM reviews
)
SELECT b.user_id AS buyer_id,
       r.user_id AS reviewer_id
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id
ORDER BY COALESCE(b.user_id, r.user_id)
LIMIT 8;

▶ Run this query in the SQL Playground

Customers who have ordered, reviewed, or both
buyer_idreviewer_id
1NULL
2NULL
NULL3
4NULL
55
66
77
88
✓ 8 rows · real output from the Playground database

Read the two columns together. Customers 5 to 8 appear in both: they ordered and reviewed. Customers 1, 2 and 4 have a NULL on the right: they ordered but never reviewed. Customer 3 has a NULL on the left: a review but no order. No one is dropped from either list.

The two WITH blocks are CTEs, named subqueries that act as the two tables. An INNER JOIN would keep only customers 5 to 8.

FULL OUTER JOIN Syntax

SQL · syntax
SELECT columns
FROM table_a
FULL OUTER JOIN table_b
  ON table_a.key = table_b.key;
PartWhat it does
FROM table_aThe left table. All of its rows are kept.
FULL OUTER JOIN table_bThe right table. All of its rows are kept too.
ON …The matching rule. Rows that satisfy it are combined into one.
FULL JOINThe same join. OUTER is optional.

Database support

DatabaseFULL OUTER JOIN
PostgreSQLSupported
SQL ServerSupported
OracleSupported
SQLiteSupported from version 3.39 (2022)
MySQL, MariaDBNot supported. Use the UNION workaround below.

How FULL OUTER JOIN Works

RowIn the resultColumns from the other table
Matches a row on the other sideOnce per matchFilled in
Left row with no matchOnceRight columns are NULL
Right row with no matchOnceLeft columns are NULL

A FULL OUTER JOIN is the result of a LEFT JOIN plus the right-table rows that the LEFT JOIN left out. Counting the four joins on the same two lists shows how they relate:

SQLite · runs in the Playground
WITH buyers AS (
  SELECT DISTINCT user_id FROM orders
), reviewers AS (
  SELECT DISTINCT user_id FROM reviews
)
SELECT 'INNER JOIN' AS join_type, COUNT(*) AS rows_returned
FROM buyers b INNER JOIN reviewers r ON r.user_id = b.user_id
UNION ALL
SELECT 'LEFT JOIN', COUNT(*)
FROM buyers b LEFT JOIN reviewers r ON r.user_id = b.user_id
UNION ALL
SELECT 'RIGHT JOIN', COUNT(*)
FROM buyers b RIGHT JOIN reviewers r ON r.user_id = b.user_id
UNION ALL
SELECT 'FULL OUTER JOIN', COUNT(*)
FROM buyers b FULL OUTER JOIN reviewers r ON r.user_id = b.user_id;

▶ Run this query in the SQL Playground

Rows returned by each join between buyers and reviewers
join_typerows_returned
INNER JOIN84
LEFT JOIN114
RIGHT JOIN104
FULL OUTER JOIN134
✓ 4 rows · real output from the Playground database
JoinRowsWho is in the result
INNER JOIN84Ordered and reviewed
LEFT JOIN114Every buyer
RIGHT JOIN104Every reviewer
FULL OUTER JOIN134Everyone on either list

The arithmetic always holds: LEFT + RIGHT − INNER = FULL, here 114 + 104 − 84 = 134.

FULL OUTER JOIN vs LEFT, RIGHT and INNER JOIN

INNERLEFTRIGHTFULL OUTER
Matching rowsKeptKeptKeptKept
Unmatched left rowsDroppedKeptDroppedKept
Unmatched right rowsDroppedDroppedKeptKept
NULLs can appear inNeither sideRight columnsLeft columnsBoth sides

Use a LEFT JOIN when one table is the master list and the other adds optional detail. Use FULL OUTER JOIN when neither table is the master: two lists that should agree, or two reports to merge. The SQL JOINs overview shows all six join types together.

FULL OUTER JOIN vs UNION

FULL OUTER JOINUNION
CombinesColumns, side by side, matched on a keyRows, stacked on top of each other
Tables needA column to match onThe same number and type of columns
A row present in bothOne combined rowOne row (UNION) or two (UNION ALL)
Tells you which side a row came fromYes, by which columns are NULLOnly if you add a label column

A UNION of the two lists would give the same 134 customers, but as a single column with no way to see who is on which list.

Finding Rows That Do Not Match

The most common real use: reconciling two sets of data.

After a FULL OUTER JOIN, a NULL key on either side marks a row that exists in only one table. Keep just those rows and you have every mismatch, from both directions, in one query:

SQLite · runs in the Playground
WITH buyers AS (
  SELECT DISTINCT user_id FROM orders
), reviewers AS (
  SELECT DISTINCT user_id FROM reviews
)
SELECT COALESCE(b.user_id, r.user_id) AS user_id,
       CASE WHEN b.user_id IS NULL THEN 'reviewed but never ordered'
            ELSE 'ordered but never reviewed' END AS issue
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id
WHERE b.user_id IS NULL
   OR r.user_id IS NULL
ORDER BY user_id
LIMIT 6;

▶ Run this query in the SQL Playground

Customers who appear on only one of the two lists
user_idissue
1ordered but never reviewed
2ordered but never reviewed
3reviewed but never ordered
4ordered but never reviewed
15ordered but never reviewed
17reviewed but never ordered
✓ 6 rows · real output from the Playground database

COALESCE picks whichever id is not NULL, so there is one id column whatever side the row came from. Grouping the same join gives a summary of all three situations:

SQLite · runs in the Playground
WITH buyers AS (
  SELECT DISTINCT user_id FROM orders
), reviewers AS (
  SELECT DISTINCT user_id FROM reviews
)
SELECT CASE WHEN b.user_id IS NULL THEN 'reviewed only'
            WHEN r.user_id IS NULL THEN 'ordered only'
            ELSE 'ordered and reviewed' END AS segment,
       COUNT(*) AS customers
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id
GROUP BY segment
ORDER BY segment;

▶ Run this query in the SQL Playground

Customers by whether they ordered, reviewed or both
segmentcustomers
ordered and reviewed84
ordered only30
reviewed only20
✓ 3 rows · real output from the Playground database

The same shape reconciles any two sources: a payments file against the orders table, last month's customer list against this month's, a staging table against production. Rows with a NULL on the left are new or unexpected; rows with a NULL on the right are missing.

You wantWHERE clause after the FULL OUTER JOIN
Rows only in the left tableWHERE b.key IS NULL
Rows only in the right tableWHERE a.key IS NULL
Rows in exactly one tableWHERE a.key IS NULL OR b.key IS NULL
Rows in bothWHERE a.key IS NOT NULL AND b.key IS NOT NULL (an INNER JOIN)

Merging Two Summaries With FULL OUTER JOIN and COALESCE

FULL OUTER JOIN is also how two summaries are put side by side when either can be missing a row. Count orders per customer and reviews per customer separately, then join the two results. A customer with orders but no reviews, or the reverse, still gets a row:

SQLite · runs in the Playground
WITH o AS (
  SELECT user_id, COUNT(*) AS orders FROM orders GROUP BY user_id
), r AS (
  SELECT user_id, COUNT(*) AS reviews FROM reviews GROUP BY user_id
)
SELECT COALESCE(o.user_id, r.user_id) AS user_id,
       COALESCE(o.orders, 0)  AS orders,
       COALESCE(r.reviews, 0) AS reviews
FROM o
FULL OUTER JOIN r
  ON r.user_id = o.user_id
ORDER BY user_id
LIMIT 8;

▶ Run this query in the SQL Playground

Orders and reviews per customer, with zeros where one is missing
user_idordersreviews
110
210
303
430
511
621
731
831
✓ 8 rows · real output from the Playground database
ExpressionWhy it is needed
COALESCE(o.user_id, r.user_id)The id may be NULL on either side, so neither column alone identifies every row.
COALESCE(o.orders, 0)A customer missing from the orders summary has NULL, which should read as 0.
Aggregate first, then join. Joining the raw orders and reviews tables on user_id would pair every order with every review of the same customer and inflate the counts.

To add names, join the result to users on the combined key:

SQLite · runs in the Playground
WITH buyers AS (
  SELECT DISTINCT user_id FROM orders
), reviewers AS (
  SELECT DISTINCT user_id FROM reviews
)
SELECT u.id, u.first_name,
       CASE WHEN b.user_id IS NOT NULL THEN 'yes' ELSE 'no' END AS has_ordered,
       CASE WHEN r.user_id IS NOT NULL THEN 'yes' ELSE 'no' END AS has_reviewed
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id
JOIN users u
  ON u.id = COALESCE(b.user_id, r.user_id)
ORDER BY u.id
LIMIT 6;

▶ Run this query in the SQL Playground

Customers with flags for having ordered and having reviewed
idfirst_namehas_orderedhas_reviewed
1Jenniferyesno
2Donnayesno
3Kennethnoyes
4Michaelyesno
5Robertyesyes
6Deborahyesyes
✓ 6 rows · real output from the Playground database

FULL OUTER JOIN in MySQL

MySQL and MariaDB do not have FULL OUTER JOIN. The standard workaround is a LEFT JOIN and a RIGHT JOIN combined with UNION, which removes the matched rows that both halves return:

MySQL
SELECT a.id, a.name, b.id, b.value
FROM a
LEFT JOIN b ON b.a_id = a.id
UNION
SELECT a.id, a.name, b.id, b.value
FROM a
RIGHT JOIN b ON b.a_id = a.id;

Running that pattern on the buyers and reviewers lists returns the same number of rows as the real FULL OUTER JOIN:

SQLite · runs in the Playground
WITH buyers AS (
  SELECT DISTINCT user_id FROM orders
), reviewers AS (
  SELECT DISTINCT user_id FROM reviews
)
SELECT COUNT(*) AS rows_from_union
FROM (
  SELECT b.user_id AS buyer_id, r.user_id AS reviewer_id
  FROM buyers b LEFT JOIN reviewers r ON r.user_id = b.user_id
  UNION
  SELECT b.user_id, r.user_id
  FROM buyers b RIGHT JOIN reviewers r ON r.user_id = b.user_id
) AS emulated;

▶ Run this query in the SQL Playground

Rows returned by the LEFT JOIN UNION RIGHT JOIN workaround
rows_from_union
134
✓ 1 row · real output from the Playground database

UNION also removes genuine duplicate rows. If the tables can contain identical rows that must be kept, use this version instead: all the LEFT JOIN rows, plus only the right-table rows that had no match.

MySQL · keeps duplicates
SELECT a.id, a.name, b.id, b.value
FROM a
LEFT JOIN b ON b.a_id = a.id
UNION ALL
SELECT a.id, a.name, b.id, b.value
FROM a
RIGHT JOIN b ON b.a_id = a.id
WHERE a.id IS NULL;

FULL OUTER JOIN With a WHERE Clause

A WHERE condition on a column of either table is not true for the rows where that table is NULL, so it removes them. Filtering on one side quietly turns the FULL OUTER JOIN into a LEFT, RIGHT or INNER JOIN:

WHERE condition onUnmatched rows that surviveEffectively a
A left-table columnLeft rows onlyLEFT JOIN
A right-table columnRight rows onlyRIGHT JOIN
Columns of both tablesNoneINNER JOIN

To filter and still keep both sides complete, filter each table before the join, in its own subquery or CTE. That is what the buyers and reviewers CTEs do. To restrict buyers to 2024 without losing reviewers, put the date condition inside the CTE:

SQLite · runs in the Playground
WITH buyers AS (
  SELECT DISTINCT user_id FROM orders
  WHERE order_date >= '2024-01-01'
), reviewers AS (
  SELECT DISTINCT user_id FROM reviews
  WHERE reviewed_at >= '2024-01-01'
)
SELECT CASE WHEN b.user_id IS NULL THEN 'reviewed only'
            WHEN r.user_id IS NULL THEN 'ordered only'
            ELSE 'ordered and reviewed' END AS segment,
       COUNT(*) AS customers
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id
GROUP BY segment
ORDER BY segment;

▶ Run this query in the SQL Playground

Customers who ordered or reviewed in 2024, by segment
segmentcustomers
ordered and reviewed25
ordered only31
reviewed only22
✓ 3 rows · real output from the Playground database

Where FULL OUTER JOIN Is Used

TaskLeft table ⟗ right table
Reconcile payments received against invoices issuedinvoices ⟗ payments
Compare a data migration: rows in the old system vs the newold_table ⟗ new_table
Customers this year vs last year (new, lost, retained)last_year ⟗ this_year
Budget vs actual spend by departmentbudget ⟗ actuals
Stock on the system vs stock counted in the warehousesystem_stock ⟗ counted_stock
Two monthly summaries merged into one reportsummary_a ⟗ summary_b
Employees in HR vs employees in payrollhr ⟗ payroll

What these have in common is that neither side is the reference. If one side is known to be complete, a LEFT JOIN from it is simpler and says so.

Common FULL OUTER JOIN Mistakes

1. Filtering one side in WHERE

❌ Reviewers with no order are removed
SQL
SELECT b.user_id, r.user_id
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id
WHERE b.user_id < 50;
✅ Filter inside the subquery
SQL
WITH b AS (
  SELECT user_id FROM buyers
  WHERE user_id < 50
)
SELECT b.user_id, r.user_id
FROM b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id;

For reviewer-only rows b.user_id is NULL, so the WHERE test fails and they disappear. The join has become a LEFT JOIN.

2. Using one side's key as the id

❌ NULL for rows from the other side
SQL
SELECT b.user_id, …
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id;
✅ COALESCE both keys
SQL
SELECT COALESCE(b.user_id, r.user_id) AS user_id, …
FROM buyers b
FULL OUTER JOIN reviewers r
  ON r.user_id = b.user_id;

Either key can be NULL. Grouping or sorting by only one of them lumps all the other side's unmatched rows together under NULL.

3. Joining detail tables directly

❌ Every order paired with every review
SQL
SELECT o.user_id, COUNT(*)
FROM orders o
FULL OUTER JOIN reviews r
  ON r.user_id = o.user_id
GROUP BY o.user_id;
✅ Summarise each table first
SQL
WITH o AS (SELECT user_id, COUNT(*) AS n FROM orders GROUP BY user_id),
     r AS (SELECT user_id, COUNT(*) AS n FROM reviews GROUP BY user_id)
SELECT COALESCE(o.user_id, r.user_id), o.n, r.n
FROM o FULL OUTER JOIN r ON r.user_id = o.user_id;

When both tables have several rows per key the join multiplies them. Reduce each to one row per key before joining.

4. Writing FULL OUTER JOIN in MySQL

❌ Syntax error in MySQL
SQL
SELECT *
FROM a
FULL OUTER JOIN b
  ON b.a_id = a.id;
✅ LEFT JOIN UNION RIGHT JOIN
SQL
SELECT * FROM a LEFT JOIN b ON b.a_id = a.id
UNION
SELECT * FROM a RIGHT JOIN b ON b.a_id = a.id;

MySQL does not implement the keyword. See FULL OUTER JOIN in MySQL.

FULL OUTER JOIN Performance

TipWhy it helps
Index the join columns on both tablesBoth sides are looked up against each other, so both need an index.
Reduce each side before joiningFiltering or aggregating in a subquery means fewer rows to match, and it keeps the join correct.
Use a narrower join when you canIf one side is always complete, LEFT JOIN gives the optimiser more options and is easier to read.
Expect the MySQL workaround to cost moreIt runs two joins and a duplicate-removal step. The UNION ALL form avoids the last one.

FULL OUTER JOIN Cheat Sheet

GoalPattern
Every row of both tablesFROM a FULL OUTER JOIN b ON b.key = a.key
One key column for every rowCOALESCE(a.key, b.key)
Rows in only one tableWHERE a.key IS NULL OR b.key IS NULL
Label which side a row came fromCASE WHEN a.key IS NULL THEN … WHEN b.key IS NULL THEN … END
Zeros instead of NULLCOALESCE(a.value, 0)
Filter without losing rowsFilter each table in a subquery or CTE before the join
MySQLLEFT JOIN … UNION … RIGHT JOIN

Practice FULL OUTER JOIN Queries

7 exercises on the real Playground tables.

Write each query yourself first. Every solution has been run against the Playground database.

  1. 1Easy Build two lists, products that have been ordered and products that have been reviewed, and FULL OUTER JOIN them. Show the first ten rows by product id. Table: order_items, reviews

    Show solution
    SQLite · runs in the Playground
    WITH ordered AS (
      SELECT DISTINCT product_id FROM order_items
    ), reviewed AS (
      SELECT DISTINCT product_id FROM reviews
    )
    SELECT o.product_id AS ordered_id,
           r.product_id AS reviewed_id
    FROM ordered o
    FULL OUTER JOIN reviewed r
      ON r.product_id = o.product_id
    ORDER BY COALESCE(o.product_id, r.product_id)
    LIMIT 10;

    ▶ Run this query in the SQL Playground

    A NULL in reviewed_id means the product was ordered but never reviewed, and the reverse for ordered_id.

  2. 2Medium Count the products that were ordered only, reviewed only, or both. Table: order_items, reviews

    Show solution
    SQLite · runs in the Playground
    WITH ordered AS (
      SELECT DISTINCT product_id FROM order_items
    ), reviewed AS (
      SELECT DISTINCT product_id FROM reviews
    )
    SELECT CASE WHEN o.product_id IS NULL THEN 'reviewed only'
                WHEN r.product_id IS NULL THEN 'ordered only'
                ELSE 'ordered and reviewed' END AS segment,
           COUNT(*) AS products
    FROM ordered o
    FULL OUTER JOIN reviewed r
      ON r.product_id = o.product_id
    GROUP BY segment
    ORDER BY segment;

    ▶ Run this query in the SQL Playground

    The side that is NULL identifies the segment. The result has 3 segments.

  3. 3Medium Compare customers who ordered in 2023 with customers who ordered in 2024. List the ones who ordered in exactly one of the two years, with the year. Table: orders

    Show solution
    SQLite · runs in the Playground
    WITH y2023 AS (
      SELECT DISTINCT user_id FROM orders
      WHERE order_date >= '2023-01-01' AND order_date < '2024-01-01'
    ), y2024 AS (
      SELECT DISTINCT user_id FROM orders
      WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01'
    )
    SELECT COALESCE(a.user_id, b.user_id) AS user_id,
           CASE WHEN a.user_id IS NULL THEN '2024 only' ELSE '2023 only' END AS year
    FROM y2023 a
    FULL OUTER JOIN y2024 b
      ON b.user_id = a.user_id
    WHERE a.user_id IS NULL
       OR b.user_id IS NULL
    ORDER BY user_id;

    ▶ Run this query in the SQL Playground

    The WHERE keeps only rows with a NULL on one side: 69 customers ordered in one year but not the other.

  4. 4Medium For each customer id, show the number of orders and the number of payments, with 0 where a customer has none of one kind. Show the first ten by id. Table: orders, payments

    Show solution
    SQLite · runs in the Playground
    WITH o AS (
      SELECT user_id, COUNT(*) AS orders FROM orders GROUP BY user_id
    ), p AS (
      SELECT user_id, COUNT(*) AS payments FROM payments GROUP BY user_id
    )
    SELECT COALESCE(o.user_id, p.user_id) AS user_id,
           COALESCE(o.orders, 0)   AS orders,
           COALESCE(p.payments, 0) AS payments
    FROM o
    FULL OUTER JOIN p
      ON p.user_id = o.user_id
    ORDER BY user_id
    LIMIT 10;

    ▶ Run this query in the SQL Playground

    Each table is reduced to one row per customer before the join, so nothing is multiplied.

  5. 5Medium Write the FULL OUTER JOIN of buyers and reviewers the MySQL way, with LEFT JOIN, RIGHT JOIN and UNION. Show the first eight rows. Table: orders, reviews

    Show solution
    SQLite · runs in the Playground
    WITH buyers AS (
      SELECT DISTINCT user_id FROM orders
    ), reviewers AS (
      SELECT DISTINCT user_id FROM reviews
    )
    SELECT buyer_id, reviewer_id
    FROM (
      SELECT b.user_id AS buyer_id, r.user_id AS reviewer_id
      FROM buyers b
      LEFT JOIN reviewers r ON r.user_id = b.user_id
      UNION
      SELECT b.user_id, r.user_id
      FROM buyers b
      RIGHT JOIN reviewers r ON r.user_id = b.user_id
    ) AS full_join
    ORDER BY COALESCE(buyer_id, reviewer_id)
    LIMIT 8;

    ▶ Run this query in the SQL Playground

    The LEFT JOIN supplies every buyer, the RIGHT JOIN every reviewer, and UNION removes the customers that both halves return. The outer query only sorts the combined rows.

  6. 6Challenging List the customers who have reviewed a product but never placed an order, with their name and city. Table: orders, reviews, users

    Show solution
    SQLite · runs in the Playground
    WITH buyers AS (
      SELECT DISTINCT user_id FROM orders
    ), reviewers AS (
      SELECT DISTINCT user_id FROM reviews
    )
    SELECT u.id, u.first_name, u.last_name, u.city
    FROM buyers b
    FULL OUTER JOIN reviewers r
      ON r.user_id = b.user_id
    JOIN users u
      ON u.id = r.user_id
    WHERE b.user_id IS NULL
    ORDER BY u.id;

    ▶ Run this query in the SQL Playground

    b.user_id IS NULL keeps reviewer-only rows. 20 customers reviewed without ordering. A LEFT JOIN from reviewers would also work here, because only one side is needed.

  7. 7Challenging For each month of 2024, show the number of orders placed and reviews written, in one row per month. Table: orders, reviews

    Show solution
    SQLite · runs in the Playground
    WITH o AS (
      SELECT strftime('%Y-%m', order_date) AS month, COUNT(*) AS orders
      FROM orders
      WHERE order_date >= '2024-01-01'
      GROUP BY month
    ), r AS (
      SELECT strftime('%Y-%m', reviewed_at) AS month, COUNT(*) AS reviews
      FROM reviews
      WHERE reviewed_at >= '2024-01-01'
      GROUP BY month
    )
    SELECT COALESCE(o.month, r.month) AS month,
           COALESCE(o.orders, 0)  AS orders,
           COALESCE(r.reviews, 0) AS reviews
    FROM o
    FULL OUTER JOIN r
      ON r.month = o.month
    ORDER BY month;

    ▶ Run this query in the SQL Playground

    Two monthly summaries merged on the month. FULL OUTER JOIN guarantees a month appears even if it has orders but no reviews, or the reverse. strftime is SQLite's date function.

📝 Quiz — FULL OUTER JOIN

Test your understanding · 0/10 answered

Question 1 of 10

What does a FULL OUTER JOIN return?

Question 2 of 10

Is FULL JOIN different from FULL OUTER JOIN?

Question 3 of 10

In a FULL OUTER JOIN result, where can NULLs appear because of a missing match?

Question 4 of 10

Which database does not support FULL OUTER JOIN?

Question 5 of 10

How do you emulate FULL OUTER JOIN in MySQL?

Question 6 of 10

LEFT JOIN returns 114 rows, RIGHT JOIN 104 and INNER JOIN 84 for two lists of unique keys. How many rows does FULL OUTER JOIN return?

Question 7 of 10

After a FULL OUTER JOIN of a and b, which WHERE clause returns rows found in only one table?

Question 8 of 10

Why wrap the keys in COALESCE(a.key, b.key)?

Question 9 of 10

What happens if you add WHERE a.status = 'x' after a FULL OUTER JOIN?

Question 10 of 10

When is FULL OUTER JOIN a better choice than LEFT JOIN?

FULL OUTER JOIN Interview Questions

Q1. What is a FULL OUTER JOIN?

A join that returns all rows from both tables. Rows satisfying the ON condition are combined; rows without a match on either side are returned with NULLs in the other table's columns.

Q2. What is the difference between FULL OUTER JOIN and LEFT JOIN?

LEFT JOIN keeps unmatched rows of the left table only. FULL OUTER JOIN also keeps the unmatched rows of the right table. FULL equals LEFT plus the right-only rows.

Q3. What is the difference between FULL OUTER JOIN and INNER JOIN?

INNER JOIN returns only matched rows. FULL OUTER JOIN returns those plus every unmatched row from both tables.

Q4. What is the difference between FULL OUTER JOIN and UNION?

A join matches rows on a key and places columns side by side. UNION stacks the rows of two queries with the same columns and does no matching. They answer different questions, though UNION is used to build a full join in MySQL.

Q5. How do you write a FULL OUTER JOIN in MySQL?

SELECT … FROM a LEFT JOIN b ON … UNION SELECT … FROM a RIGHT JOIN b ON …. To keep duplicate rows, use UNION ALL and add WHERE a.key IS NULL to the second query.

Q6. How do you find rows that exist in one table but not the other, in both directions?

FULL OUTER JOIN on the key and filter WHERE a.key IS NULL OR b.key IS NULL. A CASE expression on which key is NULL labels the direction.

Q7. How many rows can a FULL OUTER JOIN return?

At least as many as the larger table, and for unique keys exactly LEFT + RIGHT − INNER. With many-to-many matches it can exceed the sum of both tables.

Q8. Why use COALESCE with a FULL OUTER JOIN?

Because the key and any other column can be NULL on either side. COALESCE(a.key, b.key) gives a single non-NULL key, and COALESCE(value, 0) turns missing measures into zeros.

Q9. What does a WHERE filter do to a FULL OUTER JOIN?

A condition on one table's columns removes the rows where that table is NULL, reducing the join to a LEFT, RIGHT or INNER JOIN. Filter in subqueries before the join to keep both sides complete.

Q10. Can you chain FULL OUTER JOINs across three tables?

Yes, but the join key for the third table must use COALESCE of the first two keys, for example ON c.id = COALESCE(a.id, b.id), otherwise rows that came only from b fail to match.

Q11. When would you use FULL OUTER JOIN in practice?

For reconciliation and comparison: payments against invoices, old system against new after a migration, this period's customers against last period's, or budget against actuals.

Q12. Is FULL OUTER JOIN slower than other joins?

It can be, because the database must track unmatched rows on both sides and has fewer join strategies available. Indexes on both join columns and reducing each input first keep it reasonable.

Frequently Asked Questions

What is FULL OUTER JOIN in SQL?

FULL OUTER JOIN returns every row from both tables. Matching rows are combined into one, and rows with no match in the other table are still returned, with NULL in the missing columns.

Is FULL JOIN the same as FULL OUTER JOIN?

Yes. FULL JOIN is the short form; the OUTER keyword is optional and does not change the result.

Does MySQL support FULL OUTER JOIN?

No. In MySQL and MariaDB, write a LEFT JOIN and a RIGHT JOIN of the same tables and combine them with UNION. PostgreSQL, SQL Server, Oracle and SQLite 3.39 or later support FULL OUTER JOIN directly.

What is the difference between FULL OUTER JOIN and LEFT JOIN?

LEFT JOIN keeps all rows of the left table and only the matching rows of the right. FULL OUTER JOIN keeps all rows of both, so unmatched right-table rows also appear.

What is the difference between FULL OUTER JOIN and UNION?

FULL OUTER JOIN matches rows on a key and returns their columns side by side. UNION appends the rows of one query to another and does not match anything.

How do I find rows that do not match in either table?

Use a FULL OUTER JOIN and add WHERE a.key IS NULL OR b.key IS NULL. The result contains the rows that exist in only one of the two tables.

Why does FULL OUTER JOIN return NULL values?

A row with no match in the other table has no data for that table's columns, so they are NULL. With FULL OUTER JOIN this can happen on either side.

When should I use FULL OUTER JOIN?

When you need to compare or merge two sets of data and rows may be missing from either one, such as reconciling two systems or combining two summaries into one report.