Joins Explained
Every join type is the same operation with a different rule for which unmatched rows survive.
Beginner
NULLs filled in for the missing side.SELECT o.id, c.name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;
-- only orders that have a matching customer, and only customers that have a matching order
JOIN with no qualifier. Rows on either side with no match are dropped entirely.SELECT c.name, o.id AS order_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;
-- every customer, even ones with zero orders — order_id is NULL for those rows
LEFT JOIN keeps every row from the left table regardless of a match; unmatched right-side columns come back NULL. RIGHT JOIN is the mirror image and rarely used in practice — swapping table order and using LEFT JOIN is more common style.-- FULL JOIN: every row from both sides, NULLs wherever there's no match on the other side
SELECT c.name, o.id
FROM customers c
FULL JOIN orders o ON o.customer_id = c.id;
-- CROSS JOIN: every combination, no condition — n rows × m rows
SELECT size.label, color.label
FROM sizes size
CROSS JOIN colors color;
-- generating every size/color combination for a product catalog is a legitimate use
-- find employees and their manager's name, both from the same 'employees' table
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
e, m) are what make it possible to tell the two "copies" apart in the query.SUM() or COUNT() computed after that join is silently wrong, often by a lot, with no error to warn you.-- WRONG: total_spent is inflated by however many tickets each customer has
SELECT c.id, SUM(o.amount) AS total_spent, COUNT(t.id) AS ticket_count
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN tickets t ON t.customer_id = c.id
GROUP BY c.id;
-- FIX: aggregate each side separately before joining, or use a subquery per metric
SELECT c.id, o.total_spent, t.ticket_count
FROM customers c
LEFT JOIN (SELECT customer_id, SUM(amount) AS total_spent FROM orders GROUP BY customer_id) o ON o.customer_id = c.id
LEFT JOIN (SELECT customer_id, COUNT(*) AS ticket_count FROM tickets GROUP BY customer_id) t ON t.customer_id = c.id;
... ON o.customer_id = c.id -- general form, columns can have different names
... USING (customer_id) -- shorthand when both tables use the same column name