<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. SQL
  3. Joins and combining tables
All lessons in this course

Lesson 20 of 54 16 minIntermediate

LEFT, RIGHT and FULL OUTER JOIN

Keep the rows that find no match: LEFT, RIGHT and FULL joins, NULLs for the missing side, finding rows with no partner, and the two classic mistakes that turn an outer join back into an inner one.

An inner join hides customers who never ordered. Often those are exactly the rows you want: products that never sold, categories with nothing in them. An outer join keeps every row of one table (or both) and fills the columns of the missing partner with NULL.

Query.sql
SELECT c.FirstName, o.OrderId, o.OrderDateFROM dbo.Customers AS cLEFT JOIN dbo.Orders AS o    ON o.CustomerId = c.CustomerIdORDER BY c.CustomerId, o.OrderId;
Output
FirstName  OrderId  OrderDate
---------  -------  ----------
Asha             1  2024-02-03
Asha             3  2024-03-01
Ben              2  2024-02-10
Ben              9  2024-06-01
Chloe            6  2024-04-20
Dev              4  2024-03-15
Dev              7  2024-05-05
Ella          NULL  NULL
Farid            5  2024-04-02
Farid           10  2024-06-12
Grace         NULL  NULL
Zoë              8  2024-05-18

LEFT JOIN (or LEFT OUTER JOIN) keeps every row of the table on its left, here Customers. Ella and Grace now appear once each, with NULL in the Orders columns.

Finding rows with no partner

The NULLs are useful: keep only the rows where the right side is missing, and you have the customers who never ordered. Test a column that cannot be NULL in a real match, such as the right table’s primary key:

Query.sql
SELECT c.CustomerId, c.FirstName, c.JoinedOnFROM dbo.Customers AS cLEFT JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerIdWHERE o.OrderId IS NULL;
Output
CustomerId  FirstName  JoinedOn
----------  ---------  ----------
         5  Ella       2023-09-14
         7  Grace      2024-01-08

All four join types

Interactive

Outer joins: who keeps their rows?

p.Stock > 0
Join.sql
SELECT c.CategoryId, c.Name AS Category,       p.ProductId, p.Name AS Product, p.StockFROM dbo.Categories AS cLEFT JOIN dbo.Products AS p    ON p.CategoryId = c.CategoryId;
Categories c
Products p
1cat 1 · stock 40 Claw hammer
2cat 1 · stock 12 Cordless drill
3cat 1 · stock 65 Tape measure 8 m
4cat 2 · stock 300 Cement 25 kg
5cat 2 · stock 220 Sand 25 kg
6cat 3 · stock 500 Timber 2x4 2.4 m
7cat 3 · stock 80 Plywood sheet 18 mm
8cat 4 · stock 150 Wood screws (200)
9cat 4 · stock 0 Wall plugs (100)
10cat 5 · stock 25 Masonry paint 10 L
11cat 5 · stock 30 Paint roller set
12cat 6 · stock 0 Safety goggles
matchedkept, other side NULLnot in the result
CategoryIdCategoryProductIdProductStock
11Tools1Claw hammer40
21Tools2Cordless drill12
31Tools3Tape measure 8 m65
42Building4Cement 25 kg300
52Building5Sand 25 kg220
63Timber6Timber 2x4 2.4 m500
73Timber7Plywood sheet 18 mm80
84Fixings8Wood screws (200)150
94Fixings9Wall plugs (100)0
105Paint10Masonry paint 10 L25
115Paint11Paint roller set30
126Safety12Safety goggles0
137PlumbingNULLNULLNULL
13 rows. LEFT JOIN keeps every category (it is on the left); where no product matches, the product columns are NULL (amber).
JoinKeeps unmatched rows fromMissing side is
INNER JOINneither table(never missing)
LEFT JOINthe left tableNULL on the right
RIGHT JOINthe right tableNULL on the left
FULL JOINboth tablesNULL on either side

Plumbing is a category with no products yet. A LEFT JOIN from Categories shows it, and COALESCE replaces the NULL with something readable:

Query.sql
SELECT cat.Name AS Category, COALESCE(p.Name, N'(no products)') AS ProductFROM dbo.Categories AS catLEFT JOIN dbo.Products AS p ON p.CategoryId = cat.CategoryIdWHERE cat.CategoryId >= 5ORDER BY cat.CategoryId, p.Name;
Output
Category  Product
--------  ------------------
Paint     Masonry paint 10 L
Paint     Paint roller set
Safety    Safety goggles
Plumbing  (no products)

RIGHT JOIN is the same as LEFT JOIN with the two tables swapped, so Products RIGHT JOIN Categories gives the result above. Most people only use LEFT JOIN and write the table whose rows must all be kept first; a query mixing LEFT and RIGHT is hard to read.

FULL JOIN keeps unmatched rows from both sides. Products.CategoryId allows NULL, so add a gift voucher that belongs to no category, then list everything that has no partner:

Query.sql
INSERT INTO dbo.Products (Name, CategoryId, Price, Stock, Unit, AddedOn)VALUES (N'Gift voucher', NULL, 20.00, 100, N'each', '2024-06-01'); SELECT cat.Name AS Category, p.Name AS ProductFROM dbo.Categories AS catFULL JOIN dbo.Products AS p ON p.CategoryId = cat.CategoryIdWHERE cat.CategoryId IS NULL OR p.ProductId IS NULLORDER BY Category;
Output
Category  Product
--------  ------------
NULL      Gift voucher
Plumbing  NULL

Mistake 1: filtering the outer table in WHERE

Show every customer with their pending order, if they have one. Putting the status test in WHERE looks right, but returns only two customers:

Query.sql
SELECT c.FirstName, o.OrderId, o.StatusFROM dbo.Customers AS cLEFT JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerIdWHERE o.Status = 'Pending';
Output
FirstName  OrderId  Status
---------  -------  -------
Zoë              8  Pending
Ben              9  Pending

WHERE runs after the join. For customers without a pending order, o.Status is NULL or another status, the test is not true, and the row is thrown away, the very rows the LEFT JOIN kept. Move the test into ON: then it only decides which orders match, and every customer survives:

Query.sql
SELECT c.FirstName, o.OrderId, o.StatusFROM dbo.Customers AS cLEFT JOIN dbo.Orders AS o    ON o.CustomerId = c.CustomerId   AND o.Status = 'Pending'ORDER BY c.CustomerId;
Output
FirstName  OrderId  Status
---------  -------  -------
Asha          NULL  NULL
Ben              9  Pending
Chloe         NULL  NULL
Dev           NULL  NULL
Ella          NULL  NULL
Farid         NULL  NULL
Grace         NULL  NULL
Zoë              8  Pending

Mistake 2: an inner join after an outer join

Ella and Grace survive the LEFT JOIN to Orders with a NULL OrderId. The next inner join needs oi.OrderId = o.OrderId, NULL matches nothing, and they are gone again:

Query.sql
SELECT c.FirstName, o.OrderId, oi.ProductIdFROM dbo.Customers AS cLEFT JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerIdJOIN dbo.OrderItems AS oi ON oi.OrderId = o.OrderIdWHERE c.CustomerId IN (5, 6, 7)ORDER BY c.CustomerId, o.OrderId, oi.ProductId;
Output
FirstName  OrderId  ProductId
---------  -------  ---------
Farid            5         10
Farid            5         11
Farid           10          3
Farid           10          6

Once a table has been outer joined, every join hanging off it must be an outer join too:

Query.sql
SELECT c.FirstName, o.OrderId, oi.ProductIdFROM dbo.Customers AS cLEFT JOIN dbo.Orders     AS o  ON o.CustomerId = c.CustomerIdLEFT JOIN dbo.OrderItems AS oi ON oi.OrderId   = o.OrderIdWHERE c.CustomerId IN (5, 6, 7)ORDER BY c.CustomerId, o.OrderId, oi.ProductId;
Output
FirstName  OrderId  ProductId
---------  -------  ---------
Ella          NULL       NULL
Farid            5         10
Farid            5         11
Farid           10          3
Farid           10          6
Grace         NULL       NULL
Quick check

Customers LEFT JOIN Orders … WHERE o.Status = 'Shipped' returns the same rows as…

Quick check

How do you find products that appear on no order line?

Practice

List every customer with their shipped orders (OrderId and OrderDate). Customers who have no shipped order must still appear, with NULLs. Sort by CustomerId, then OrderId.

Show a hint

LEFT JOIN Orders, and put both the key match and the Status test in ON.

Show the solution
Solution.sql
SELECT c.FirstName, o.OrderId, o.OrderDateFROM dbo.Customers AS cLEFT JOIN dbo.Orders AS o    ON o.CustomerId = c.CustomerId   AND o.Status = 'Shipped'ORDER BY c.CustomerId, o.OrderId;
Output
FirstName  OrderId  OrderDate
---------  -------  ----------
Asha             1  2024-02-03
Asha             3  2024-03-01
Ben              2  2024-02-10
Chloe         NULL  NULL
Dev              4  2024-03-15
Dev              7  2024-05-05
Ella          NULL  NULL
Farid            5  2024-04-02
Farid           10  2024-06-12
Grace         NULL  NULL
Zoë           NULL  NULL

Remember

  • LEFT JOIN keeps every row of the left table; unmatched rows get NULL in the right table’s columns.
  • RIGHT JOIN is LEFT JOIN with the tables swapped; FULL JOIN keeps unmatched rows from both sides.
  • LEFT JOIN … WHERE right.Key IS NULL finds rows with no partner.
  • Conditions on the right table belong in ON; in WHERE they turn the outer join into an inner join.
  • After an outer join, the joins that hang off that table must be outer joins too.
0 of 54 lessons completed