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.
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;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
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;CustomerId FirstName JoinedOn
---------- --------- ----------
5 Ella 2023-09-14
7 Grace 2024-01-08All four join types
| Join | Keeps unmatched rows from | Missing side is |
|---|---|---|
INNER JOIN | ||
LEFT JOIN | ||
RIGHT JOIN | ||
FULL JOIN |
COALESCE replaces the NULL with something readable:
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;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:
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;Category Product -------- ------------ NULL Gift voucher Plumbing NULL
Mistake 1: filtering the outer table in WHERE
SELECT c.FirstName, o.OrderId, o.StatusFROM dbo.Customers AS cLEFT JOIN dbo.Orders AS o ON o.CustomerId = c.CustomerIdWHERE o.Status = 'Pending';FirstName OrderId Status --------- ------- ------- Zoë 8 Pending Ben 9 Pending
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:
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;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
oi.OrderId = o.OrderId, NULL matches nothing, and they are gone again:
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;FirstName OrderId ProductId --------- ------- --------- Farid 5 10 Farid 5 11 Farid 10 3 Farid 10 6
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;FirstName OrderId ProductId --------- ------- --------- Ella NULL NULL Farid 5 10 Farid 5 11 Farid 10 3 Farid 10 6 Grace NULL NULL
Show a hint
Show the solution
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;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.