Sample Tables — Simple Data
All JOIN examples below use these two tiny tables so you can see exactly what each JOIN returns. Keep them in mind — every output table is derived from these rows.
employees
| employee_id | first_name | department_id | salary |
|---|---|---|---|
| 101 | Alice | 10 | 70000 |
| 102 | Bob | 20 | 80000 |
| 103 | Charlie | 10 | 75000 |
| 104 | David | NULL | 60000 |
Note: David has no department (NULL).
Visual: How INNER, LEFT, RIGHT and FULL OUTER JOINs relate — keep the 4×3 sample tables in mind.
departments
| department_id | department_name |
|---|---|
| 10 | HR |
| 20 | Engineering |
| 30 | Marketing |
Note: Marketing (30) has no employees.
Why JOIN?
Real databases separate data into related tables. JOIN brings them together.
INNER JOIN
INNER JOIN returns only rows where the join condition matches in both tables.
SELECT e.first_name, d.department_name
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id;
Expected Output (only matching rows)
| first_name | department_name |
|---|---|
| Alice | HR |
| Bob | Engineering |
| Charlie | HR |
David (NULL dept) and Marketing (30, no employees) are excluded. 3 rows.
Returns matching records only.
LEFT JOIN
LEFT JOIN returns all rows from the left table and matching rows from the right, filling missing matches with NULLs.
SELECT d.department_name, e.first_name
FROM departments d
LEFT JOIN employees e
ON d.department_id = e.department_id;
Expected Output (all departments)
| department_name | first_name |
|---|---|
| HR | Alice |
| HR | Charlie |
| Engineering | Bob |
| Marketing | NULL |
Marketing has no match → first_name is NULL. David not shown because query starts from departments.
All departments, including those with no employees.
RIGHT JOIN
RIGHT JOIN returns all rows from the right table and matching rows from the left, the mirror of LEFT JOIN.
SELECT e.first_name, d.department_name
FROM employees e
RIGHT JOIN departments d
ON e.department_id = d.department_id;
Expected Output (all employees)
| first_name | department_name |
|---|---|
| Alice | HR |
| Bob | Engineering |
| Charlie | HR |
| David | NULL |
David (NULL dept) appears with NULL department_name. Marketing not shown.
FULL OUTER JOIN
FULL OUTER JOIN returns all rows from both tables, matching where possible and filling gaps with NULLs on either side.
SELECT e.first_name, d.department_name
FROM employees e
FULL OUTER JOIN departments d
ON e.department_id = d.department_id;
Expected Output (everyone)
| first_name | department_name |
|---|---|
| Alice | HR |
| Bob | Engineering |
| Charlie | HR |
| David | NULL |
| NULL | Marketing |
5 rows: 3 matches + 1 unmatched employee + 1 unmatched department.
MySQL has no native FULL OUTER JOIN — use UNION of LEFT and RIGHT.
SELF JOIN
SELF JOIN joins a table to itself, treating the same table as two logical copies — essential for employee-manager hierarchies.
SELECT e.first_name AS employee, m.first_name AS manager
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.employee_id;
Sample: employees as e and m (manager)
| employee | manager |
|---|---|
| Alice (101) | — (manager NULL) |
| Bob (102) | Alice |
| Charlie (103) | Alice |
| David (104) | Bob |
Uses e.manager_id = m.employee_id with table aliases e (employee) and m (manager).
Same table as two logical copies (e and m).
CROSS JOIN
CROSS JOIN produces the Cartesian product, combining every row from one table with every row from the other table.
SELECT a.team_name AS team_a, b.team_name AS team_b
FROM teams a
CROSS JOIN teams b;
Expected: Cartesian Product
With 4 employees × 3 departments = 12 rows (every combination). Example first 3 rows:
| first_name | department_name |
|---|---|
| Alice | HR |
| Alice | Engineering |
| Alice | Marketing |
… 9 more rows (rarely used alone — usually with filtering).
4 × 5 = 20 rows — every combination.
Employee Earning More Than Manager
This classic interview problem finds employees who earn more than their manager by self-joining the employees table on manager_id.
SELECT e.first_name AS employee_name, e.salary, m.first_name AS manager_name, m.salary
FROM employees e
JOIN employees m
ON e.manager_id = m.employee_id
WHERE e.salary > m.salary;
Expected Output (employees earning more than manager)
| employee_name | employee_salary | manager_name | manager_salary |
|---|---|---|---|
| Bob | 80000 | Alice | 70000 |
| Charlie | 75000 | Alice | 70000 |
David not shown (manager is Bob, 80000 > 60000 false). Alice has no manager.
Key: e.manager_id = m.employee_id
Multi-Table JOIN
Multi-table JOIN connects more than two tables in sequence, following relationships from employees to departments to jobs.
SELECT e.first_name, d.department_name, j.job_title
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN jobs j ON e.job_id = j.job_id;
Expected Output (three tables joined)
| first_name | department_name | job_title |
|---|---|---|
| Alice | HR | Engineer |
| Bob | Engineering | Manager |
| Charlie | HR | Analyst |
David excluded (no department). Marketing excluded (no employee).