MySQL Joins

A join combines rows from two or more tables based on a related column, letting you query normalized data as if it were one connected table.

Why Joins Exist

Relational databases split data into separate tables to avoid repeating information - a customers table holds each customer once, and an orders table holds each order once, referring back to the customer by id rather than repeating the customer's full name and address on every order row. This is normalization, and it keeps data consistent, but it means the information you actually want - 'show me each order with the customer's name' - lives across two tables. A join is the operation that stitches those tables back together at query time, using a shared column, usually a foreign key, to line up matching rows.

The Basic Shape of a Join

Orders paired with the customer who placed them

SELECT orders.order_id, customers.name, orders.total_amount
FROM orders
JOIN customers ON orders.customer_id = customers.customer_id;

The ON clause is the heart of the join - it tells MySQL exactly which column in each table should be compared to decide whether two rows belong together. Get the ON condition wrong, and you either lose rows you wanted or gain rows you didn't; the FROM and JOIN keywords just say which tables are involved.

The Join Types

  • INNER JOIN returns only rows that have a match in both tables - a customer with no orders, or an order with a missing customer_id, is left out entirely.
  • LEFT JOIN returns every row from the left table, filling in NULLs for any columns from the right table when there's no match.
  • RIGHT JOIN is the mirror image of LEFT JOIN - every row from the right table is kept, with NULLs filling in for the left table.
  • A SELF JOIN isn't a different keyword - it's the same table joined to itself under two aliases, used for hierarchical or peer relationships like 'employee reports to manager'.
  • MySQL has no FULL OUTER JOIN keyword, but the same result - unmatched rows from either side - can be built by combining a LEFT JOIN and a RIGHT JOIN with UNION.

Orders whose customer_id doesn't match any real customer

SELECT o.order_id, o.customer_id
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
WHERE c.customer_id IS NULL;

Table Aliases Keep Joins Readable

Once a query joins two or more tables, typing the full table name in front of every column gets noisy fast, and it's required whenever both tables share a column name like id. Aliasing each table with a short letter or two - o for orders, c for customers - keeps queries compact and makes every column reference unambiguous about which table it came from.

Chaining joins across four related tables

SELECT o.order_id, c.name, p.product_name, oi.quantity
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id;
Join TypeLeft rows kept?Right rows kept?Unmatched side shows as
INNER JOINOnly if matchedOnly if matchedRow dropped entirely
LEFT JOINAlwaysOnly if matchedNULLs on the right
RIGHT JOINOnly if matchedAlwaysNULLs on the left
Note: Read a join out loud in plain English before you trust it - 'every order, joined with its customer if one exists' should describe exactly what the query says, keyword for keyword.
Note: A join with a missing or always-true ON condition becomes a cross join, pairing every row of one table with every row of the other. A 1,000-row table joined that way against another 1,000-row table returns a million rows - always double-check that your ON clause references real, related columns.

Exercise: MySQL Joins

What rows does an INNER JOIN return?