Phase 3 · Joins: combining tables · Topic 3.2

LEFT JOIN: keep every row from one side

Must know

A LEFT JOIN keeps every row from the left (first) table, joins matching rows from the right table, and fills the right-hand columns with NULL where there is no match.

Not started

Step 1 Understand it

Why you need it. "Show every department with its headcount" must include HR with 0. "Every customer and what they spent" must include Omar, who spent nothing. An silently drops them. keeps them.

Watch it happen

AnimationLEFT JOIN: keep every row of the left table
Read the animation as text
  1. LEFT JOIN keeps every row of the left table — here departments — whether or not it finds a match.
  2. Engineering matches 4 employees, Sales 2, Marketing 1. HR matches nobody.
  3. Matched departments appear once per match. HR still appears — once — with NULL in every employee column.
  4. Counting per department: COUNT(e.emp_id) skips HR's NULL and gives 0. COUNT(*) counts HR's one NULL-filled row and wrongly says 1.

Picture it

flowchart LR
  subgraph L["departments (left): all kept"]
    D1["Engineering"]
    D2["Sales"]
    D4["HR"]
  end
  D1 --> R1["Engineering · Asha<br/>Engineering · Ben<br/>…"]
  D2 --> R2["Sales · Dara<br/>Sales · Eli"]
  D4 --> R4["HR · NULL<br/>kept, with NULLs"]
Every of the left survives. Matches are filled in as usual; a row with no match (HR) is kept once, with in all the right-hand .

The idea

In departments d LEFT JOIN employees e, departments is the left table and every department is kept. Where employees match, you get one row per match, as with INNER JOIN. Where none match — HR — you still get one row, with NULL in every employees column. Count with COUNT(e.emp_id), not COUNT(*), so HR's NULL row counts as 0. is the mirror image (keep every row of the right table); people almost always swap the tables and write a LEFT JOIN instead.

The shape of it

sql
SELECT a.column, b.column
FROM table_a AS a
LEFT JOIN table_b AS b ON b.key = a.key;

Words in lower case are the parts you fill in.

How it works, step by step

  1. Start with every row of the left table, departments: Engineering, Sales, Marketing, HR.
  2. For each, find matching employees. Engineering has 4, so it appears 4 times.
  3. HR has no matching employees, so it appears once, with NULL for the employee columns.
  4. COUNT(e.emp_id) counts only non-NULL ids: 4, 2, 1 and 0 for HR.
  5. COUNT(*) would wrongly give HR 1, because there is one (NULL-filled) row.

Analogy

A class register. Every pupil's name is on it (the left table). Next to each name you write the homework they handed in (the right table). A pupil who handed nothing in stays on the register with a blank next to their name.

Where the analogy breaks

A blank on a register is just empty. In the blank is NULL, and NULL behaves strangely: COUNT(*) still counts that row as 1, and a on a right-hand column can throw the row away again.

Worked examples

Every department, with its people

sql
SELECT d.dept_name, e.name
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.dept_id
ORDER BY d.dept_id, e.name;

8 rows

dept_namename
EngineeringAsha
EngineeringBen
EngineeringChen
EngineeringHana
SalesDara
SalesEli
MarketingFatima
HRNULL

HR is kept with a NULL name. Gus still isn't here: departments is the side being kept, and he matches no department.

Headcount, with zeros

sql
SELECT d.dept_name, COUNT(e.emp_id) AS headcount
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.dept_id
GROUP BY d.dept_id, d.dept_name
ORDER BY d.dept_id;

4 rows

dept_nameheadcount
Engineering4
Sales2
Marketing1
HR0

COUNT(e.emp_id) skips the NULL, so HR shows 0. With COUNT(*), HR would show 1.

Every customer and what they spent

sql
SELECT c.customer_id, c.name,
       COUNT(o.order_id) AS orders,
       COALESCE(SUM(o.amount), 0) AS total_spent
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.name
ORDER BY c.customer_id;

6 rows

customer_idnameorderstotal_spent
1Priya5425
2Tom2275
3Lena260
4Omar00
5Zoe133
6Tom00

Omar and the second Tom are kept with 0 orders. over their NULL row is NULL, so turns it into 0.

Must-know points

  • LEFT JOIN keeps every row of the left table; unmatched rows get NULLs on the right.
  • Put the table you want to keep in full first ("every customer" → customers first).
  • Count the right side with COUNT(right.column), not COUNT(*), to get 0 for no matches.
  • Wrap sums of the right side in COALESCE(…, 0).
  • RIGHT JOIN is LEFT JOIN with the tables swapped; prefer LEFT JOIN for readability.

Common mistakes

  • Using COUNT(*) after a LEFT JOIN and getting 1 instead of 0 for rows with no match.
  • Adding WHERE right_table.column = …, which throws the NULL rows away and turns the LEFT JOIN back into an INNER JOIN (see Join traps).
  • Putting the tables the wrong way round, so the side you wanted to keep is on the right.

In other databases

  • LEFT JOIN and LEFT OUTER JOIN are the same thing everywhere. RIGHT JOIN only arrived in in version 3.39 (2022); it has always worked in the other .

Step 2 Go further (free, optional)

Optional. The notes above are written to be enough on their own. These free resources give a second explanation and more practice.

Step 3 Practise

Try each one yourself first: out loud, or written and run in the practice box below, which saves every query you run. Then open the answer and compare.

  1. ExplainSay what it does and when to use it, in plain words, with no notes.

    Explain why HR appears in a LEFT JOIN from departments but not in an INNER JOIN.

    Show answer

    A LEFT JOIN promises to keep every row of the left table, here departments, whether or not it finds a match. HR has no employees, so it gets one row with NULL in every employee column.

    An INNER JOIN keeps only pairs that match. HR has nobody to pair with, so it is dropped.

    That is why "show every department, even empty ones" needs LEFT JOIN, with departments on the left.

  2. Writefrom a blank page, then run it

    Write a listing every product with the total quantity sold, showing 0 for products never sold.

    Show answer

    Products on the left, so every product stays. SUM of no rows is NULL, so wrap it in COALESCE to show 0 for the phone case.

    sql
    SELECT p.product_name, COALESCE(SUM(oi.quantity), 0) AS total_sold
    FROM products p
    LEFT JOIN order_items oi ON oi.product_id = p.product_id
    GROUP BY p.product_id, p.product_name
    ORDER BY p.product_id;

    7 rows

    product_nametotal_sold
    Pen5
    Notebook5
    Mug6
    Desk lamp6
    Backpack4
    Headphones11
    Phone case0
  3. Predictsay it before you run it

    How many rows does SELECT * FROM customers c LEFT JOIN orders o ON o.customer_id = c.customer_id; return?

    Show answer

    12 rows. Each customer appears once per order: Priya 5, Tom (id 2) 2, Lena 2, Zoe 1. That is 10. Customers with no orders still get one row each, with NULLs: Omar and the second Tom (id 6). 10 + 2 = 12.

    The query below shows just three columns so it fits on screen.

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

    12 rows

    customer_idnameorder_id
    1Priya101
    1Priya102
    1Priya103
    1Priya105
    1Priya109
    2Tom104
    2Tom108
    3Lena106
    3Lena107
    4OmarNULL
    5Zoe110
    6TomNULL
  4. Edge casesSay what happens with NULLs, ties, duplicates or no rows at all, and fix it.

    In the headcount example, change COUNT(e.emp_id) to COUNT(*). Which number changes, and why?

    Show answer

    HR changes from 0 to 1. The other departments stay the same.

    The LEFT JOIN gives HR one row, with NULLs in the employee columns. COUNT(*) counts rows, so it counts that row: 1. COUNT(e.emp_id) counts only values that are not NULL, and HR's emp_id is NULL: 0.

    After a LEFT JOIN, count a column from the right-hand table (ideally its key), not *.

    sql
    SELECT d.dept_name, COUNT(e.emp_id) AS count_emp_id, COUNT(*) AS count_star
    FROM departments d
    LEFT JOIN employees e ON e.dept_id = d.dept_id
    GROUP BY d.dept_id, d.dept_name
    ORDER BY d.dept_id;

    4 rows

    dept_namecount_emp_idcount_star
    Engineering44
    Sales22
    Marketing11
    HR01

Practice box: write and run your answers here

Ctrl/⌘ + Enter runs it. Select part of the query to run only that part.

Step 4 Interview-ready, then prove it

Your interview answer

About 20 to 40 seconds, said out loud.

A LEFT JOIN returns every row from the left table and the matching rows from the right; where there is no match, the right-hand columns are NULL. I use it whenever the question says "all" or "every" — every customer, including those with no orders. After a LEFT JOIN I count a right-hand column rather than star, so unmatched rows give zero, and I COALESCE sums to zero.

Follow-up questions

LEFT JOIN or INNER JOIN — how do you choose?

Ask whether rows without a match should appear. "Every customer" means LEFT JOIN; "customers who ordered" means INNER JOIN.

Can a LEFT JOIN return more rows than the left table has?

Yes. A left row that matches three right rows appears three times. It returns at least as many rows as the left table.

Tick each check when you can do it without notes

A topic is finished when you pass all four checks, not when you have read it. Be strict: if you had to look at the notes, it is not ticked yet.

  • Explain Say what it does and when to use it, in plain words, with no notes.
  • Write Write the query from a blank page, then run it to check.
  • Predict Read a query and say which rows it returns before you run it.
  • Edge cases Say what happens with NULLs, ties, duplicates or no rows at all, and fix it.

0 of 4 checks ticked.

Saved in this browser. Back it up or move it to another device.