Tutorial

Full Outer Join In Sql: 2026 Guide & Examples

Master the full outer join in sql with practical examples, billing reconciliation queries, syntax rules, and NULL handling for data analysts.

Anuj SainiMar 18, 2026Updated Sep 16, 202621 min read

When auditing enterprise ledgers or synchronizing distributed microservices, relying on INNER JOIN or LEFT JOIN blinds you to one side of your data pipeline. Executing a full outer join in sql provides complete 360-degree visibility across both tables by retaining every row from both the left and right datasets regardless of whether a matching relational key exists. If you need to perform billing reconciliation between Stripe charges and your internal Postgres invoice database, or detect orphaned records across customer tracking platforms, the SQL full outer join is the definitive set-operation tool.

In relational database management systems like PostgreSQL, Snowflake, BigQuery, and SQL Server, an outer join preserves non-matching rows by padding missing columns with NULL. Understanding how to correctly query, filter, and coalesce these columns is one of the most critical competencies evaluated in technical SQL interviews and senior analytics engineering roles.

For deeper gotchas when joining one-to-many relationships, study our companion guides on SQL JOIN Fan-Out, our deep-dive on LEFT JOIN vs LEFT OUTER JOIN, and our troubleshooting manual for SQL NULL traps.


Practice on Live PostgreSQL Engines

Every query and reconciliation pattern in this guide can be tested directly on Topfolio Practice with real PostgreSQL databases. Sharpen your skills with dedicated JOIN modules designed around real enterprise schemas.

SQL JOIN Types at a Glance

Before examining deep reconciliation mechanics, compare how each standard ANSI join behaves when two tables share common keys alongside unmatched entries:

Join OperationLeft Table Unmatched RowsMatched RowsRight Table Unmatched RowsPrimary Production Use Case
INNER JOINDroppedPreservedDroppedStrict relational lookups where both keys must exist
LEFT JOINPreserved (Right = NULL)PreservedDroppedPrimary entity retention with optional supplemental attributes
RIGHT JOINDroppedPreservedPreserved (Left = NULL)Secondary entity retention (rarely used; rewritten as LEFT)
FULL OUTER JOINPreserved (Right = NULL)PreservedPreserved (Left = NULL)Two-way audits, financial ledger reconciliation, data diffs
CROSS JOINCartesian Product ($N \times M$)Cartesian ProductCartesian ProductCalendar matrices, price grid expansion, test mocks

The Setup: Our Example Tables

We will use two straightforward tables throughout the baseline examples to illustrate how unmatched keys behave:

customers

customer_idnamecity
1AliceMumbai
2BobDelhi
3CharlieBangalore
4DianaChennai

orders

order_idcustomer_idamountorder_date
10115002026-01-15
10213002026-02-20
10327002026-01-22
10452002026-03-01

Notice the intentional gaps in this dataset:

  • Customer 3 (Charlie) and Customer 4 (Diana) have placed zero orders.
  • Order 104 references customer_id = 5, which does not exist in the customers dimension table (an orphaned transaction).

INNER JOIN — Only Matching Rows

An INNER JOIN evaluates the join predicate and returns exclusively the records where matching keys exist in both participating tables.

sql
SELECT
  c.name,
  c.city,
  o.order_id,
  o.amount
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;

Result:

namecityorder_idamount
AliceMumbai101500
AliceMumbai102300
BobDelhi103700

What is dropped: Charlie and Diana are eliminated because they have no records in orders. Order 104 is eliminated because its customer_id (5) does not exist in customers. Use INNER JOIN only when non-matching rows are irrelevant to your query grain.


LEFT JOIN — All Left Table + Matching Right

A LEFT JOIN (or LEFT OUTER JOIN) preserves every row from the left-hand table. If a row in the left table matches one or more rows in the right table, those attributes are appended. If no match exists, the right-hand attributes populate with NULL.

sql
SELECT
  c.name,
  c.city,
  o.order_id,
  o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;

Result:

namecityorder_idamount
AliceMumbai101500
AliceMumbai102300
BobDelhi103700
CharlieBangaloreNULLNULL
DianaChennaiNULLNULL

What is dropped: Order 104 is still omitted because its key does not exist in the primary left table (customers).

Why LEFT JOIN Dominates Reporting Queries

In business intelligence pipelines, you rarely want to drop active customers simply because they haven't made a purchase this month. LEFT JOIN guarantees base population integrity while pulling in optional transactional activity.


RIGHT JOIN — All Right Table + Matching Left

A RIGHT JOIN is the exact structural mirror of LEFT JOIN. It guarantees that every record from the right-hand table appears in the final result, populating missing left-hand columns with NULL.

sql
SELECT
  c.name,
  o.order_id,
  o.amount,
  o.customer_id
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id;

Result:

nameorder_idamountcustomer_id
Alice1015001
Alice1023001
Bob1037002
NULL1042005

In production SQL codebases, senior data engineers almost universally convert RIGHT JOIN operations into LEFT JOIN syntax by reversing the table sequence. Reading queries from left to right establishes a cleaner cognitive hierarchy.


How to Use FULL OUTER JOIN in SQL (Syntax & Mechanics)

The ANSI SQL standard defines FULL OUTER JOIN (syntactically equivalent to FULL JOIN) as a bidirectional union of outer-join operations. It evaluates the join predicate and produces:

  1. Inner Matches: Rows where table_a.key = table_b.key, merging attributes horizontally.
  2. Left-Unmatched Rows: Rows from Table A where no key matches Table B, populating Table B columns with NULL.
  3. Right-Unmatched Rows: Rows from Table B where no key matches Table A, populating Table A columns with NULL.

Basic Baseline Syntax

sql
SELECT
  c.customer_id AS customer_table_id,
  c.name,
  c.city,
  o.order_id,
  o.customer_id AS orders_table_id,
  o.amount
FROM customers c
FULL OUTER JOIN orders o ON c.customer_id = o.customer_id;

Query Output:

customer_table_idnamecityorder_idorders_table_idamount
1AliceMumbai1011500
1AliceMumbai1021300
2BobDelhi1032700
3CharlieBangaloreNULLNULLNULL
4DianaChennaiNULLNULLNULL
NULLNULLNULL1045200

How RDBMS Execution Engines Execute Full Outer Joins

When you execute a full outer join in SQL, the database query planner (such as PostgreSQL's cost-based optimizer) chooses between three core physical operators depending on table statistics, indexing, and available working memory (work_mem):

  1. Hash Full Join: The planner builds an in-memory hash table on the inner relation. As it probes the outer relation, it marks matched hash buckets. Once probe scanning concludes, the engine performs an additional pass across the hash table to emit all unmarked buckets padded with NULL columns.
  2. Merge Full Join: If both inputs are sorted on the join key (via an existing B-tree index or an explicit Sort operator), the engine steps through both streams in lockstep, emitting matches and advancing whichever stream lags behind while padding nulls.
  3. Nested Loop with Materialization: Only selected for tiny tables or when non-equi join conditions prevent hash or merge strategies.

Full Outer Join in SQL Practical Guide: Reconciling Billing Records

The single most prevalent real-world application of FULL OUTER JOIN in enterprise analytics is bilateral financial reconciliation.

Consider a high-growth SaaS business reconciling third-party payment gateway charges (Stripe Gateway) against the internal application ledger (PostgreSQL Billing Service). Discrepancies emerge constantly due to webhook drops, delayed authorizations, API timeouts, manual customer support adjustments, or direct payment platform chargebacks.

The Production Schema & Scenario

Examine the two distinct tables loaded into your data warehouse:

stripe_charges (Ingested via Stripe Webhook Fivetran / Airbyte sync)

charge_idcustomer_emailamount_centsstatus
ch_101alice@topfolio.in5000succeeded
ch_102bob@topfolio.in3000succeeded
ch_103evan@topfolio.in4500succeeded
ch_104frank@topfolio.in2000succeeded

app_invoices (Internal Postgres application billing table)

invoice_idcustomer_emailamount_centsplan_tier
inv_901alice@topfolio.in5000pro_monthly
inv_902bob@topfolio.in3000pro_monthly
inv_903charlie@topfolio.in7500enterprise
inv_904frank@topfolio.in2500pro_monthly

An analyst auditing end-of-month cash balances must classify every single row into one of four critical reconciliation buckets:

  1. Clean Match: The customer was billed in the app and charged the exact identical amount on Stripe.
  2. Amount Discrepancy: The transaction exists in both systems, but the collected amount differs (e.g. Frank was invoiced $25.00 but Stripe charged only $20.00).
  3. Orphaned Gateway Charge (Phantom): Stripe charged Evan $45.00, but no matching invoice exists in the application database (potential unlinked customer or webhook failure).
  4. Uncollected Invoice (Overdue): Charlie has an internal invoice for $75.00, but no payment record exists in Stripe (unpaid account or failed billing attempt).

The Reconciliation Query

Here is the production-grade reconciliation query implementing FULL OUTER JOIN with COALESCE key harmonization and categorized discrepancy flags:

sql
SELECT
  -- 1. Unify the reconciliation key across both systems
  COALESCE(s.customer_email, a.customer_email) AS unified_email,
  
  -- 2. Preserve raw transactional identifiers
  s.charge_id AS stripe_charge_id,
  a.invoice_id AS app_invoice_id,
  
  -- 3. Extract financial metrics in cents
  s.amount_cents AS stripe_cents,
  a.amount_cents AS invoice_cents,
  
  -- 4. Calculate cash variance (handling nulls with COALESCE)
  COALESCE(s.amount_cents, 0) - COALESCE(a.amount_cents, 0) AS variance_cents,
  
  -- 5. Classify the ledger state
  CASE
    WHEN s.charge_id IS NOT NULL AND a.invoice_id IS NOT NULL AND s.amount_cents = a.amount_cents
      THEN 'MATCHED'
    WHEN s.charge_id IS NOT NULL AND a.invoice_id IS NOT NULL AND s.amount_cents != a.amount_cents
      THEN 'AMOUNT_MISMATCH'
    WHEN s.charge_id IS NOT NULL AND a.invoice_id IS NULL
      THEN 'ORPHANED_STRIPE_CHARGE'
    WHEN s.charge_id IS NULL AND a.invoice_id IS NOT NULL
      THEN 'UNCOLLECTED_APP_INVOICE'
  END AS audit_status
FROM stripe_charges s
FULL OUTER JOIN app_invoices a
  ON s.customer_email = a.customer_email
ORDER BY unified_email ASC;

The Reconciled Ledger Output

unified_emailstripe_charge_idapp_invoice_idstripe_centsinvoice_centsvariance_centsaudit_status
alice@topfolio.inch_101inv_901500050000MATCHED
bob@topfolio.inch_102inv_902300030000MATCHED
charlie@topfolio.inNULLinv_903NULL7500-7500UNCOLLECTED_APP_INVOICE
evan@topfolio.inch_103NULL4500NULL+4500ORPHANED_STRIPE_CHARGE
frank@topfolio.inch_104inv_90420002500-500AMOUNT_MISMATCH

Notice the analytical clarity provided by the FULL OUTER JOIN:

  • If you had used an INNER JOIN, Charlie, Evan, and Frank's pricing mismatch would have either disappeared or required complex secondary queries.
  • If you had used a LEFT JOIN starting from app_invoices, Evan's unauthorized or unlinked $45.00 charge would have remained completely invisible to accounting.

Handling NULL Matches and Non-Coalesced Keys

When executing a full outer join in SQL, working with NULLs is unavoidable. Failing to anticipate NULL behavior in SELECT, WHERE, and aggregate clauses leads to silent bugs.

1. The Single-Column Join Key Trap

The most frequent bug in junior SQL queries is selecting the join key from only one table:

sql
-- WRONG: If Charlie has no stripe record, s.customer_email returns NULL!
SELECT
  s.customer_email,
  s.amount_cents,
  a.amount_cents
FROM stripe_charges s
FULL OUTER JOIN app_invoices a ON s.customer_email = a.customer_email;

If a row originates solely from the right table (app_invoices), s.customer_email evaluates to NULL. The resulting dataset appears to contain anonymous or corrupt records.

The Fix: Always wrap join keys in COALESCE:

sql
-- CORRECT: Returns the first non-null identifier from either side
SELECT
  COALESCE(s.customer_email, a.customer_email) AS customer_email,
  s.amount_cents,
  a.amount_cents
FROM stripe_charges s
FULL OUTER JOIN app_invoices a ON s.customer_email = a.customer_email;

2. The WHERE Clause Filter Pushdown Trap

A devastating mistake in analytics queries is appending a WHERE filter on an outer-joined table.

Consider this query intended to find high-value discrepancies:

sql
-- TRAP QUERY: Unintentionally converts FULL OUTER JOIN into a RIGHT JOIN!
SELECT
  COALESCE(s.customer_email, a.customer_email) AS customer_email,
  s.amount_cents AS stripe_cents,
  a.amount_cents AS invoice_cents
FROM stripe_charges s
FULL OUTER JOIN app_invoices a ON s.customer_email = a.customer_email
WHERE s.amount_cents > 2000;

Why this breaks: For every customer without a Stripe charge (such as Charlie), s.amount_cents is NULL. In standard three-valued SQL logic, NULL > 2000 evaluates to UNKNOWN. The WHERE clause filters out any row that does not evaluate to TRUE. Consequently, all records where Stripe is missing are instantly dropped! The query silently turns into an INNER JOIN or LEFT JOIN.

The Two Correct Solutions:

Solution A (Filter in the ON clause):

sql
-- Evaluates the condition during the join phase, retaining unmatched records
SELECT
  COALESCE(s.customer_email, a.customer_email) AS customer_email,
  s.amount_cents AS stripe_cents,
  a.amount_cents AS invoice_cents
FROM stripe_charges s
FULL OUTER JOIN app_invoices a
  ON s.customer_email = a.customer_email
  AND s.amount_cents > 2000;

Solution B (Null-safe WHERE predicate):

sql
-- Explicitly allows NULL values generated by the outer join
SELECT
  COALESCE(s.customer_email, a.customer_email) AS customer_email,
  s.amount_cents AS stripe_cents,
  a.amount_cents AS invoice_cents
FROM stripe_charges s
FULL OUTER JOIN app_invoices a ON s.customer_email = a.customer_email
WHERE s.amount_cents > 2000 OR s.amount_cents IS NULL;

3. Aggregate Math on Nullable Columns

In arithmetic expressions, any operation involving NULL yields NULL (NULL - 500 = NULL). When calculating variances, always use COALESCE(column, 0).

Similarly, distinguish between COUNT(*) and COUNT(column):

  • COUNT(*) counts every row emitted by the full outer join, including rows where one side was absent.
  • COUNT(s.charge_id) counts only rows where a Stripe charge was present.

Full Outer Join in SQL Examples: Full Anti-Join (Symmetric Difference)

A standard anti-join isolates records in Table A that have no counterpart in Table B (LEFT JOIN ... WHERE b.key IS NULL).

A Full Anti-Join (also known as a symmetric difference) isolates records that exist in either Table A or Table B, but not in both. This query is the ultimate audit filter to surface data drift between two systems.

sql
-- Full Anti-Join: Returns strictly unmatched rows from either side
SELECT
  COALESCE(s.customer_email, a.customer_email) AS discrepancy_email,
  s.charge_id AS stripe_charge,
  a.invoice_id AS app_invoice
FROM stripe_charges s
FULL OUTER JOIN app_invoices a
  ON s.customer_email = a.customer_email
WHERE s.customer_email IS NULL 
   OR a.customer_email IS NULL;

Result:

discrepancy_emailstripe_chargeapp_invoice
charlie@topfolio.inNULLinv_903
evan@topfolio.inch_103NULL

Alice, Bob, and Frank are completely excluded because they exist in both tables. This query immediately delivers an actionable queue for financial operations teams.


How to Use Full Outer Join in SQL When Dialects Lack Native Support (MySQL)

A frequent technical interview question asks: "How do you write a full outer join in MySQL when MySQL does not support the FULL OUTER JOIN keyword?"

MySQL syntax throws ERROR 1064 (42000): You have an error in your SQL syntax if you attempt to use FULL OUTER JOIN or FULL JOIN.

To simulate a full outer join in MySQL, combine a LEFT JOIN and a RIGHT JOIN using the UNION set operator:

sql
-- MySQL Emulation of FULL OUTER JOIN
-- Step 1: All customers with matching orders + customers without orders
SELECT
  c.customer_id AS customer_id,
  c.name,
  o.order_id,
  o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
 
UNION
 
-- Step 2: All orders without matching customers
SELECT
  c.customer_id AS customer_id,
  c.name,
  o.order_id,
  o.amount
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL;

Why UNION Rather Than UNION ALL?

If you simply run LEFT JOIN ... UNION ALL ... RIGHT JOIN, all matching rows (Alice and Bob) appear twice in the result set. By either:

  1. Using UNION (which incurs an internal sorting and deduplication step), or
  2. Adding WHERE c.customer_id IS NULL to the RIGHT JOIN half and combining via UNION ALL (recommended for large datasets because it avoids a costly full-table deduplication pass),

you produce the exact mathematical result set of an ANSI FULL OUTER JOIN.


Master Complex Joins in the Data Analyst Track

Build muscle memory writing multi-table joins, billing reconciliations, and window functions across 100+ real PostgreSQL interview benchmarks.

Explore the Data Analyst Track

CROSS JOIN — Every Combination

A CROSS JOIN produces the Cartesian product of two tables. Every row from the first table is paired with every row from the second table. No ON condition is permitted.

sql
SELECT
  c.name,
  p.product_name
FROM customers c
CROSS JOIN products p;

If customers has 4 rows and products has 3 rows, the result contains $4 \times 3 = 12$ rows.

Practical Analytical Use Case: Calendar Matrix Scaffolding

In financial reporting, business stakeholders demand reports showing revenue for every product across every month, including months with zero sales. An INNER JOIN drops zero-sales periods.

By combining CROSS JOIN with LEFT JOIN, you create an unbroken reporting spine:

sql
SELECT
  m.month,
  p.product_name,
  COALESCE(SUM(s.revenue), 0) AS total_revenue
FROM months m
CROSS JOIN products p
LEFT JOIN sales s
  ON m.month = s.sale_month
  AND p.product_id = s.product_id
GROUP BY m.month, p.product_name
ORDER BY m.month, p.product_name;

Watch Out for Cartesian Explosions

Crossing two 10,000-row tables generates 100,000,000 rows. If executed in production without restrictive date filters, Cartesian products will saturate your database memory buffer pool and trigger out-of-memory (OOM) query terminations.


Self JOIN — Joining a Table to Itself

A self-join is not a distinct SQL keyword, but an architectural pattern where a single table is referenced multiple times using distinct table aliases. It is used to query hierarchical trees or compare rows within the same dataset.

Use Case 1: Organizational Hierarchy (Employee to Manager)

Given an employees table where each row contains an employee_id and an optional manager_id:

sql
SELECT
  e.name AS employee,
  COALESCE(m.name, 'Executive Board / CEO') AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;
employeemanager
AliceExecutive Board / CEO
BobAlice
CharlieAlice
DianaBob

Use Case 2: Finding Pairs Within the Same Group

To find pairs of customers residing in the same city without generating identical self-matches or duplicate permutations:

sql
SELECT
  c1.name AS customer_1,
  c2.name AS customer_2,
  c1.city
FROM customers c1
JOIN customers c2
  ON c1.city = c2.city
  AND c1.customer_id < c2.customer_id;

The strict inequality predicate `c1.customer_id < c2.customer_id` guarantees:

  1. Customers are never paired with themselves (customer_id != customer_id).
  2. If (Alice, Bob) is returned, the inverse duplicate (Bob, Alice) is omitted.

Anti-JOIN Patterns — Finding Missing Records

Anti-joins identify rows in one dataset that have zero matching entries in another. There are three standard methods:

sql
SELECT c.*
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;

This pattern returns customers who have never placed an order (Charlie and Diana). Query optimizers easily recognize this syntax and transform it into efficient Hash Anti-Join execution plans.

Method 2: NOT EXISTS (Ideal for Correlated Subqueries)

sql
SELECT c.*
FROM customers c
WHERE NOT EXISTS (
  SELECT 1 
  FROM orders o
  WHERE o.customer_id = c.customer_id
);

NOT EXISTS is semantically explicit and short-circuits as soon as a single matching record is located in the target table.

Method 3: NOT IN (The NULL Trap Danger)

sql
SELECT c.*
FROM customers c
WHERE c.customer_id NOT IN (
  SELECT customer_id FROM orders
);

Why NOT IN Fails Silently with NULL Values

If even a single customer_id inside the orders subquery is NULL, the entire NOT IN predicate evaluates to UNKNOWN for all rows. As a result, the query returns zero rows, completely concealing existing records. Never use NOT IN unless the target column has an explicit NOT NULL constraint. Prefer NOT EXISTS or LEFT JOIN ... WHERE IS NULL.


Multiple JOINs — Chaining Multi-Table Queries

Enterprise data warehouses structure schemas into third normal form (3NF) or star schemas requiring chains of 3 to 6 joins:

sql
SELECT
  c.name AS customer_name,
  o.order_id,
  p.product_name,
  oi.quantity,
  oi.unit_price,
  (oi.quantity * oi.unit_price) AS line_total
FROM customers c
JOIN orders o 
  ON c.customer_id = o.customer_id
JOIN order_items oi 
  ON o.order_id = oi.order_id
JOIN products p 
  ON oi.product_id = p.product_id
WHERE o.order_date >= '2026-01-01'
ORDER BY o.order_id, oi.item_id;

Guidelines for Multi-Table Joins

  • Explicit Aliasing: Always prefix selected columns with clear table aliases (c.name, o.order_id). Ambiguous column errors occur when multiple tables share column names (e.g. created_at, status).
  • Join Order Hierarchy: Position your primary dimensional entity on the left and chain child bridge tables sequentially before attaching peripheral lookup dimensions.

Performance Optimization & Query Tuning for Joins

Executing joins across tables with millions of rows requires careful resource management:

  1. Foreign Key Indexing: Ensure all join key columns are indexed with B-tree indexes. Without indexes on foreign keys, databases must default to sequential table scans.
  2. Push Predicates to WHERE / CTEs: Reduce table cardinality before joining. Filtering out historical years in a Common Table Expression (CTE) drastically reduces the memory allocated to join hash tables.
  3. Tune Memory Allocation (work_mem): In PostgreSQL, if the hash table for an in-memory Hash Join exceeds work_mem, the planner spills hash batches to temporary disk files, degrading query throughput by up to 100x.
  4. Avoid Expressions in Join Predicates: Writing ON LOWER(c.email) = LOWER(u.email) prevents standard index lookups. Use functional expression indexes or clean data at ingestion time.

9. Try It Yourself in the Topfolio Practice Sandbox

Theoretical knowledge without query execution leads to interview failure. You can test join mechanics, spot join fan-out bugs, and practice queries directly in our PostgreSQL browser sandbox on Four-Table JOIN: Invoice Details.

The question loads a relational billing schema with 412 invoices and 2,240 line items. Test these two queries in the live code editor:

Query 1: The Naive Fan-Out Trap (8.95x Ledger Inflation)

Joining the invoice table directly to child invoiceline records without considering relationship cardinality:

sql
-- Trap Query: Fan-out multiplication across 2,240 line items
SELECT ROUND(SUM(i.total), 2) AS fan_out_revenue
FROM invoice AS i
JOIN invoiceline AS il ON i.invoiceid = il.invoiceid;

Sandbox Output: 20,848.62

Because single invoices span multiple line items, the join repeats each invoice total across every purchased line item, inflating the true 2,328.60 total by nearly 9x.

Query 2: The Pre-Aggregated Clean Fix

Preserve the true grain of one row per invoice before aggregating:

sql
-- Fix Query: Deduplicate join keys to preserve 1-row-per-invoice grain
SELECT ROUND(SUM(i.total), 2) AS clean_revenue
FROM (SELECT invoiceid, total FROM invoice) AS i
JOIN (SELECT DISTINCT invoiceid FROM invoiceline) AS il
  ON i.invoiceid = il.invoiceid;

Sandbox Output: 2,328.60

The deduplication preserves the true grain of one row per invoice, ensuring your aggregate matches the genuine ledger balance.


Practice Real SQL JOIN Problems

Test your query optimization skills on real PostgreSQL sandboxes with instant execution benchmarks and company interview tests.

Start Practicing Free

Frequently Asked Questions

What does a full outer join in sql do?

A full outer join in sql returns all rows from both participating tables. When rows satisfy the join condition, columns combine side-by-side; when a row has no corresponding match in the opposite table, the database fills all columns from the missing side with NULL values.

How do you handle NULL values after a FULL OUTER JOIN?

To handle NULL values after a FULL OUTER JOIN, wrap non-matching identifiers and metrics in COALESCE. For example, use COALESCE(a.id, b.id) to retain a unified account key, and COALESCE(metric, 0) to prevent numeric calculations from propagating null values.

What is the difference between FULL OUTER JOIN and UNION ALL?

FULL OUTER JOIN merges columns horizontally based on a shared key condition, producing matched rows and side-padded NULL columns. UNION ALL stacks rows vertically from queries with identical schema without evaluating relational keys or removing duplicates.

Does MySQL support FULL OUTER JOIN syntax directly?

No, MySQL does not natively support the FULL OUTER JOIN keyword. To emulate a full outer join in MySQL, run a LEFT JOIN and a RIGHT JOIN with an IS NULL filter, combining both queries using a UNION operator.

Why does a WHERE clause turn a FULL OUTER JOIN into an INNER JOIN?

Filtering right or left table attributes in a WHERE clause using non-null predicates (such as WHERE table_b.status = 'active') discards all outer-joined rows where table_b was NULL, unintentionally converting the outer join into an inner join. Move predicates to the ON clause instead.

Anuj Saini

Written by

Anuj SainiFounder & Lead Instructor

Founder at Topfolio with 6+ years in data & analytics across JPMC, Ultrahuman, and high-growth startups. Sat on hiring panels, reviewed 500+ resumes, and writes practical SQL & data guides.