Keeping every row with LEFT JOIN

Keep all rows from one table, find what's missing, and pick the right join for the question.

Intermediate9 min readLesson 15 of 38
In this lesson 4 sections
  1. Counting with zeros
  2. Finding rows with no match
  3. The trap: filtering the right table in WHERE
  4. Which join should I use?
Jump to practice

INNER JOIN drops rows without a match. LEFT JOIN keeps every row from the left table (the one in FROM) and fills the right table’s columns with NULL where nothing matched.

Using the same small tables as lesson 14:

SELECT a.account_id, a.name, o.opportunity_id, o.amount
FROM accounts AS a
LEFT JOIN opportunities AS o
       ON o.account_id = a.account_id;
account_id name opportunity_id amount
1 Acme 101 40,000
1 Acme 102 15,000
2 Birch 103 60,000
3 Cedar NULL NULL

Cedar stays this time, with NULL deal columns. Deal 104 still drops out, because it only exists in the right table.

Counting with zeros

LEFT JOIN fixes the problem from lesson 12, where groups with no rows disappear:

SELECT a.name,
       COUNT(o.opportunity_id) AS deals
FROM accounts AS a
LEFT JOIN opportunities AS o ON o.account_id = a.account_id
GROUP BY a.name;

Cedar shows 0. Count a column from the right table, not COUNT(*): COUNT(*) counts Cedar’s one NULL-filled row and shows 1.

Finding rows with no match

After a LEFT JOIN, unmatched rows have NULL in every right-hand column. Filter for that to find what’s missing:

-- Accounts with no opportunities at all
SELECT a.account_id, a.name
FROM accounts AS a
LEFT JOIN opportunities AS o ON o.account_id = a.account_id
WHERE o.opportunity_id IS NULL;

Test a right-hand column that can never be NULL in a real match, such as its ID.

The trap: filtering the right table in WHERE

SELECT a.name, o.opportunity_id
FROM accounts AS a
LEFT JOIN opportunities AS o ON o.account_id = a.account_id
WHERE o.stage = 'Negotiation';   -- Cedar disappears again

Cedar’s stage is NULL, and NULL = 'Negotiation' isn’t TRUE, so WHERE throws it away. Your LEFT JOIN has silently become an INNER JOIN. To keep every account, move the condition into ON:

LEFT JOIN opportunities AS o
       ON o.account_id = a.account_id
      AND o.stage = 'Negotiation'

Which join should I use?

The question The pattern
“Show deals with their account name” FROM opportunities INNER JOIN accounts
“List every account, with deals if it has any” FROM accounts LEFT JOIN opportunities
“Which accounts have no deals?” LEFT JOIN, then WHERE right-side ID IS NULL

When in doubt, start FROM the table whose rows you must not lose, LEFT JOIN the rest, and check the row count.

RIGHT JOIN exists too. It’s a LEFT JOIN with the tables swapped, so most people just swap the tables.

Try it

List every opportunity with its account name and the name of the rep who owns it. Keep deals that don’t have an owner yet. Return the deal name, account name and rep name. Field guide exercise J1 is a longer version of this.

Stretch: which accounts have more than 10 opportunities? That one combines joins, GROUP BY and HAVING: exercise B4.

Write it in the sandbox below, then press Check answer.

SQL sandbox

Run queries against the sample CRM tables (accounts, opportunities,users, contacts and opportunity_history), right in your browser. Nothing you run leaves your computer.

Back to the top of the lesson