Phase 3 · Joins: combining tables · Topic 3.1

Joins, and INNER JOIN

Must know

A JOIN combines rows from two tables wherever a condition (usually matching keys) is true, and INNER JOIN keeps only the rows that found a match on both sides.

Not started

Step 1 Understand it

Why you need it. The employees knows Ben is in department 1, but not that department 1 is called Engineering. That lives in departments. To show "Ben — Engineering" you need from both tables in one . That is a .

New words in this topic (tap one to see what it means)

Watch it happen

AnimationINNER JOIN: keep the pairs that match on both sides
Read the animation as text
  1. A join pairs rows from two tables wherever the ON condition is true — here, where the dept_ids match.
  2. Ben has dept_id 1. Engineering has dept_id 1. That pair matches, so it becomes one result row: Ben, Engineering.
  3. Every employee is matched the same way: 7 pairs. Gus (NULL) matches nothing — NULL never equals anything. And no employee points at HR.
  4. INNER JOIN keeps only the matched pairs, each with columns from both tables. Gus and HR are simply not there.

Picture it

flowchart LR
  subgraph E["employees"]
    Ben["Ben · dept 1"]
    Dara["Dara · dept 2"]
    Gus["Gus · dept NULL"]
  end
  subgraph D["departments"]
    D1["1 · Engineering"]
    D2["2 · Sales"]
    D4["4 · HR"]
  end
  Ben -->|"match"| D1
  Dara -->|"match"| D2
  Gus -.->|"no match: dropped"| X["not in result"]
  D4 -.->|"no match: dropped"| X
keeps only pairs that match on the key. Gus ( department) and HR (no employees) have no partner, so neither appears in the result.

The idea

You name two tables and a join condition after : employees e JOIN departments d ON d.dept_id = e.dept_id. The pairs every employee with every department whose dept_id matches, and each pair becomes one result row holding the columns of both. INNER JOIN (plain JOIN means the same thing) keeps only pairs that matched. So Gus, whose dept_id is NULL, matches nothing and disappears — and HR, which no employee points to, disappears too. The short names e and d are ; they save typing and say which table each column comes from.

The shape of it

sql
SELECT a.column, b.column
FROM table_a AS a
INNER 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. FROM employees AS e: take every employee and call the table e.
  2. JOIN departments AS d ON d.dept_id = e.dept_id: for each employee, find departments with the same dept_id.
  3. Ben (dept_id 1) matches Engineering, so one row: Ben, Engineering.
  4. Gus (dept_id NULL) matches nothing, because NULL never equals anything — so INNER JOIN drops him.
  5. HR (dept_id 4) is never matched by any employee, so it doesn't appear either.
  6. picks e.name and d.dept_name from each matched pair.

Analogy

Matching odd socks from two baskets by colour. A red sock from basket A is paired with a red sock from basket B. INNER JOIN only keeps the pairs; any sock without a partner stays in its basket.

Where the analogy breaks

Socks pair . Joins don't have to: if one department matches four employees, the department's row is repeated four times — once per partner. That repetition is normal, but it is also where comes from (see Join traps).

Worked examples

Employees with their department name

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

7 rows

namedept_name
AshaEngineering
BenEngineering
ChenEngineering
DaraSales
EliSales
FatimaMarketing
HanaEngineering

7 rows, not 8: Gus is gone. HR is gone too. INNER JOIN shows only matches.

Orders with the customer's name

sql
SELECT o.order_id, c.name, o.amount
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
WHERE o.order_date < '2024-02-01'
ORDER BY o.order_id;

4 rows

order_idnameamount
101Priya120
102Priya80
103Priya45
104Tom200

JOIN on its own means INNER JOIN, and is optional for table aliases. Priya's name repeats once per order — that is at work.

Join, then group: revenue per city

sql
SELECT c.city, SUM(o.amount) AS revenue
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
GROUP BY c.city
ORDER BY revenue DESC;

3 rows

cityrevenue
London485
Leeds275
NULL33

The join brings the city onto each order; then adds them up. Zoe's order lands in the NULL city group.

Must-know points

  • A join pairs rows from two tables where the ON condition is true; each pair is one result row.
  • INNER JOIN (or just JOIN) keeps only matched rows — unmatched rows on either side vanish.
  • NULL keys never match, so rows with a NULL key always drop out of an INNER JOIN.
  • Table aliases (e, d) keep short. Always prefix columns with them once there is more than one table.
  • One-to-many joins repeat the "one" side once per match. That is expected.

Common mistakes

  • Forgetting the ON condition, which pairs every row with every row (a ): 8 × 4 = 32 rows.
  • Leaving out the prefix on a column both tables have (dept_id), which gives an "ambiguous column" error.
  • Using INNER JOIN when the question says "all customers" or "every department" — the ones with no match disappear.

In other databases

  • Old-style joins list tables with commas and put the condition in : FROM employees e, departments d WHERE d.dept_id = e.dept_id. It works everywhere, but explicit JOIN … ON is clearer and is what interviewers expect.

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 what INNER JOIN does with Gus and with HR, and why.

    Show answer

    INNER JOIN pairs each employee with the department whose dept_id matches theirs, and keeps only the pairs where ON d.dept_id = e.dept_id is true.

    Gus: his dept_id is NULL. NULL equals nothing, not even another NULL, so he matches no department and is dropped.

    HR: it is department 4, and no employee has dept_id 4. It matches no employee, so it is dropped too.

    So INNER JOIN quietly loses rows from both sides that have no partner. You get 7 rows, not 8 employees or 4 departments. If you need the unmatched rows, use a LEFT (or FULL) JOIN.

  2. Writefrom a blank page, then run it

    Write a query listing each order_items row with the product name and the quantity, for order 105.

    Show answer

    Start from order_items, since that is where the quantity is, and join products to swap product_id for a name. Order 105 has 1 backpack and 3 headphones: 30 + 3 × 40 = £150, its amount.

    sql
    SELECT oi.order_id, p.product_name, oi.quantity
    FROM order_items oi
    JOIN products p ON p.product_id = oi.product_id
    WHERE oi.order_id = 105
    ORDER BY p.product_name;

    2 rows

    order_idproduct_namequantity
    105Backpack1
    105Headphones3
  3. Predictsay it before you run it

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

    Show answer

    10 rows, one per order. Every order has exactly one customer: customer_id is and points at a real customer. So each order finds one partner, and none is lost or repeated.

    Omar and the second Tom have no orders, so they don't appear, but that doesn't change the count: you start from orders. (It also returns 10 columns, 5 from each table, so the check below just counts.)

    sql
    SELECT COUNT(*) AS joined_rows
    FROM orders o
    JOIN customers c ON c.customer_id = o.customer_id;

    1 row

    joined_rows
    10
  4. Edge casesSay what happens with NULLs, ties, duplicates or no rows at all, and fix it.

    Which employees vanish from SELECT e.name, m.name FROM employees e JOIN employees m ON m.emp_id = e.manager_id;? Why?

    Show answer

    Asha vanishes. 7 rows come back, not 8.

    The join looks for the row m whose emp_id equals e's manager_id. Asha's manager_id is NULL, so no row matches her, and an INNER JOIN drops rows with no match. To keep her, with NULL as her manager, use .

    (The aliases employee and manager are added below, because both columns would otherwise be called name.)

    sql
    SELECT e.name AS employee, m.name AS manager
    FROM employees e
    JOIN employees m ON m.emp_id = e.manager_id
    ORDER BY e.emp_id;

    7 rows

    employeemanager
    BenAsha
    ChenAsha
    DaraAsha
    EliDara
    FatimaAsha
    GusDara
    HanaBen

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 join combines rows from two tables based on a condition, normally a matching a . INNER JOIN returns only rows that match on both sides, so rows with no partner — or a NULL key — are excluded. In a one-to-many join the one side repeats for each match, which is correct but means I have to be careful when summing columns from the one side.

Follow-up questions

Is JOIN the same as INNER JOIN?

Yes. JOIN on its own is an inner join.

Does a join need a foreign key to exist?

No. Any condition works, even ON a.x > b.y. Foreign keys just make the matching column reliable.

Why did my join return fewer rows than the table has?

Rows without a match — or with a NULL key — are dropped by an inner join. Use a LEFT JOIN to keep them.

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.