SQL JOINs Explained: INNER, LEFT, RIGHT & FULL
9 min read · Updated August 13, 2026
Real data lives in more than one table. A JOIN
combines rows from two tables based on a related column between them — for example, matching each
Employees.dept_id to
a Departments.dept_id.
We'll use these two small tables throughout:
| first_name | dept_id |
|---|---|
| Ananya | 10 |
| Tomas | 20 |
| Priya | 30 |
| Wei | 99 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Sales |
| 40 | Support |
Notice: Priya's department (30) doesn't exist in Departments, Wei's department (99) doesn't exist either, and Support (40) has no employees yet. These mismatches are exactly what make the different join types behave differently.
INNER JOIN — only the matches
INNER JOIN returns rows only when the join condition matches on both sides. Anything that doesn't match is dropped.
SELECT e.first_name, d.dept_name
FROM Employees e
INNER JOIN Departments d ON d.dept_id = e.dept_id;
Result: Ananya/Engineering and Tomas/Sales. Priya and Wei are dropped (no matching department), and Support doesn't appear (no matching employee).
LEFT JOIN — everything from the left table
LEFT JOIN keeps every row from the "left" table (the one named right after FROM), and fills in
NULL for the
right-hand columns when there's no match.
SELECT e.first_name, d.dept_name
FROM Employees e
LEFT JOIN Departments d ON d.dept_id = e.dept_id;
Result: all four employees appear. Priya and Wei get NULL for dept_name.
This is the join to reach for when you're asking "show me every X, and whatever related Y exists (if any)" — for example, every product alongside its total orders, including products that have never been ordered.
RIGHT JOIN — everything from the right table
The mirror image of LEFT JOIN: every row from the right-hand table is kept, with NULLs on the left where there's no match.
SELECT e.first_name, d.dept_name
FROM Employees e
RIGHT JOIN Departments d ON d.dept_id = e.dept_id;
Result: Ananya/Engineering, Tomas/Sales, and NULL/Support — Support has no employees, but the department still appears.
In practice, most people just swap the table order and use LEFT JOIN instead — the results are identical, and it reads more naturally for most queries.
FULL OUTER JOIN — everything, matched where possible
FULL JOIN keeps every row from both tables, matching where it can and filling NULLs on whichever side has no counterpart.
SELECT e.first_name, d.dept_name
FROM Employees e
FULL OUTER JOIN Departments d ON d.dept_id = e.dept_id;
Result: all four employees and all three departments, including Priya/NULL, Wei/NULL, and NULL/Support.
Quick reference
| Join type | Keeps |
|---|---|
| INNER JOIN | Only rows that match on both sides |
| LEFT JOIN | All left rows, matched right rows or NULL |
| RIGHT JOIN | All right rows, matched left rows or NULL |
| FULL OUTER JOIN | Every row from both, matched where possible |
A three-table join
Joins chain naturally — add as many as you need:
SELECT c.company, o.order_id, p.name AS product
FROM Orders o
JOIN Customers c ON c.customer_id = o.customer_id
JOIN Products p ON p.product_id = o.product_id;
Try it yourself
The Playground has Employees, Departments, Customers, Products and Orders all pre-loaded — run every join above exactly as written.
Open the Playground