LEFT JOIN Syntax
SELECT columns
FROM left_table
LEFT JOIN right_table ON left_table.col = right_table.col;
-- LEFT OUTER JOIN is identical β OUTER keyword is optional
SELECT columns
FROM left_table
LEFT OUTER JOIN right_table ON left_table.col = right_table.col;
-- Include ALL employees, even those without a department
SELECT e.id, e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
-- Result:
-- id | name | dept_name
-- 1 | Alice | Marketing
-- 2 | Bob | Engineering
-- 3 | Carol | Engineering
-- 4 | Dave | HR
-- 5 | Eve | NULL β Eve kept; no matching department
-- Finance is still excluded (it is on the RIGHT side with no match)
-- Finance would appear with LEFT JOIN if departments is the LEFT table
NULLs in LEFT JOIN Results
When a left-table row has no match in the right table, all right-table columns in the result are NULL. You can test these NULLs to identify unmatched rows.
-- Show employees with their department name (or 'No Department')
SELECT
e.name,
COALESCE(d.dept_name, 'No Department') AS department
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
-- Count employees per department, including departments with 0 employees
-- NOTE: swap the table order so departments is on the LEFT
SELECT
d.dept_name,
COUNT(e.id) AS employee_count -- COUNT(e.id) counts non-NULL β gives 0 for empty depts
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.id
GROUP BY d.dept_name
ORDER BY employee_count DESC;
When counting with a LEFT JOIN, use COUNT(right_table.id) not COUNT(*). COUNT(*) counts all result rows including the NULL-filled ones, inflating the count. COUNT(column) only counts non-NULL values, correctly giving 0 for groups with no matches.
RIGHT JOIN
RIGHT JOIN is the mirror of LEFT JOIN β it keeps all rows from the right table and fills NULLs for unmatched left-table columns.
-- RIGHT JOIN: keep all departments, even those with no employees
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id;
-- Result:
-- name | dept_name
-- Alice | Marketing
-- Bob | Engineering
-- Carol | Engineering
-- Dave | HR
-- NULL | Finance β Finance kept; no matching employee
-- Eve is excluded (she has no department β unmatched on LEFT side)
-- Equivalent LEFT JOIN (swap table order β same result)
SELECT e.name, d.dept_name
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.id;
LEFT vs RIGHT: Which to Use?
| LEFT JOIN | RIGHT JOIN | |
|---|---|---|
| All rows from | Left (first) table | Right (second) table |
| NULLs for | Unmatched right columns | Unmatched left columns |
| Rewrite as | β | LEFT JOIN with tables swapped |
| Preferred? | Yes β use this by default | Avoid β swap tables and use LEFT |
Every RIGHT JOIN can be rewritten as a LEFT JOIN by swapping the table order. Most SQL developers use exclusively LEFT JOIN to keep queries consistent and easier to reason about. Mixing LEFT and RIGHT in the same query is a common source of confusion.
Finding Unmatched Rows (Anti-Join)
The anti-join pattern finds rows in one table that have no corresponding row in another. Use LEFT JOIN and filter where the right-table key IS NULL.
-- Find employees with no department assigned
SELECT e.id, e.name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id
WHERE d.id IS NULL;
-- Returns: Eve (id=5, dept_id=NULL)
-- Find departments with no employees
SELECT d.id, d.dept_name
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.id
WHERE e.id IS NULL;
-- Returns: Finance (id=4)
-- Find customers who have never placed an order
SELECT c.id, c.name, c.email
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
-- Find products that have never been ordered
SELECT p.id, p.product_name
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.id
WHERE oi.id IS NULL;
Summary
π Summary
- LEFT JOIN β All rows from the left table. NULLs for right-table columns when no match.
- RIGHT JOIN β All rows from the right table. Equivalent to swapping tables and using LEFT JOIN.
- Prefer LEFT JOIN β More readable and consistent. Avoid mixing LEFT and RIGHT in one query.
- COALESCE β Use to replace NULLs in output:
COALESCE(d.dept_name, 'Unassigned'). - COUNT(right_col) β Use column count, not COUNT(*), to avoid inflated counts with LEFT JOIN.
- Anti-join pattern β LEFT JOIN + WHERE right_table.id IS NULL finds unmatched rows efficiently.
FAQ
Yes β this is a very common mistake. If you write LEFT JOIN departments d ON e.dept_id = d.id WHERE d.dept_name = 'Engineering', the WHERE filters out NULL rows (where d.dept_name is NULL), effectively converting the LEFT JOIN to an INNER JOIN. To keep unmatched rows while filtering the right table, move the condition into the ON clause: LEFT JOIN departments d ON e.dept_id = d.id AND d.dept_name = 'Engineering'.
Yes. Chain multiple LEFT JOINs: each one keeps all rows from the result so far and adds NULLs for unmatched rows in the next table. Be careful to apply each join to the correct table and check that WHERE clauses on right-table columns do not accidentally eliminate the NULL rows you want to keep.
All three can find unmatched rows, but each has trade-offs. LEFT JOIN + IS NULL is often the most performant and index-friendly. NOT IN has a dangerous NULL trap (if the subquery contains a NULL, NOT IN returns no rows). NOT EXISTS is semantically clean and handles NULLs correctly, but is slower on some databases. For production code, LEFT JOIN + IS NULL or NOT EXISTS are the safest choices.
When a department has no employees, the LEFT JOIN adds one NULL-filled row for that department. COUNT(*) counts that row, giving 1 instead of 0. COUNT(e.id) only counts non-NULL values in the employee id column, correctly returning 0 for departments with no employees. Always use COUNT(right_table_column) when counting matches in a LEFT JOIN.