Dataslope logoDataslope

Outer Joins

Keeping unmatched rows on purpose — LEFT, RIGHT, and FULL joins — and why NULLs appear where a match is missing.

An inner join silently drops rows that have no match. But sometimes the unmatched rows are exactly what you want to see: "customers who have never ordered," "products that have never sold." For those questions you need an outer join, which deliberately keeps unmatched rows.

LEFT JOIN: keep all rows from the left table

A LEFT JOIN keeps every row from the left (first) table, matched or not. Where a match exists, the right table's columns are filled in. Where none exists, those columns come back as NULL.

The key insight: the left table is fully preserved. The right table is "optional" — it contributes data when it can and NULLs when it cannot.

SQL
PostgreSQL 17

Linus appears, with NULL for order_id and amount — the join's honest way of saying "this customer matched nothing." An inner join would have hidden him entirely.

Finding the unmatched rows

A LEFT JOIN plus WHERE ... IS NULL is the classic recipe for "find the rows with no match." Keep everyone, then keep only the ones whose match came back empty:

SQL
PostgreSQL 17

Linus and Mae have no orders, so their joined o.id is NULL, and the filter keeps exactly them. This pattern — outer join, then test for NULL — answers a whole family of "who is missing?" questions.

RIGHT and FULL joins

Once LEFT JOIN makes sense, its siblings are easy:

  • RIGHT JOIN keeps every row from the right table instead of the left. It is just a LEFT JOIN with the tables mentally swapped — most people simply reorder the tables and use LEFT.
  • FULL JOIN keeps every row from both tables, filling NULLs on whichever side lacks a match.

A simple way to choose

Ask: "whose rows must I keep even without a match?" Keep the left table → LEFT JOIN. Keep both → FULL JOIN. Keep only matched rows → plain INNER JOIN. Naming the table you refuse to lose tells you the join.

Why NULLs appear — and that it is correct

The NULLs in an outer join are not a glitch; they are the truth. There genuinely is no order for Linus, so "his order amount" is genuinely unknown/absent — exactly what NULL means (recall the Working with NULL page). Outer joins and NULL are natural partners: the join preserves a row, and NULL faithfully marks the information that the missing side could not supply.

Check your understanding

QuestionSelect one

What does a LEFT JOIN keep that an INNER JOIN does not?

Nothing; they are identical.

Every row from the left table, even those with no match in the right table (filling NULLs for the missing side).

Only rows that match in both tables.

Every row from the right table only.

QuestionSelect one

In a LEFT JOIN of customers to orders, a customer with no orders appears with what in the order columns?

Zeros.

Empty strings.

NULLs, marking that there was no matching order to supply those values.

The previous customer's order values.

QuestionSelect one

Which pattern finds "customers who have never placed an order"?

An inner join between customers and orders.

A LEFT JOIN from customers to orders, then WHERE o.id IS NULL.

A LEFT JOIN then WHERE o.id IS NOT NULL.

SELECT DISTINCT on the orders table.

QuestionSelect one

A FULL JOIN between two tables returns which rows?

Only the rows that match in both tables.

Only the left table's rows.

Every row from both tables, with NULLs wherever a row on one side has no match on the other.

No rows, because both sides must match.

On this page