TL;DR:
If you’re using a LEFT JOIN
but filtering on the right table in the WHERE
clause, you might unintentionally turn it into an INNER JOIN
. Here’s why that happens, how to fix it, and how different databases handle it.
🧩 The Issue: LEFT JOIN + WHERE = INNER JOIN?
Let’s start with the basics. The purpose of a LEFT JOIN
is to return:
- All records from the left table
- Matching records from the right table
- NULLs from the right table if there’s no match
So, you expect unmatched rows to still appear.
But what if you write this query?
sqlCopyEditSELECT *
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
WHERE d.name = 'Sales';
Surprise!
This behaves just like an INNER JOIN
. All employees who aren’t in the “Sales” department — or who have no department at all — are filtered out. That’s because:
WHERE d.name = 'Sales'
excludes any row whered.name
isNULL
, which happens for unmatched joins.
🛠️ Fixes: How to Keep the LEFT JOIN Behavior
✅ Fix #1: Move Conditions into the JOIN Clause
Move the right-table filter into the ON
clause:
sqlCopyEditSELECT *
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id AND d.name = 'Sales';
🔍 This ensures:
- Unmatched employees (those with no department) still appear.
- Only “Sales” departments are matched, others are treated as non-matches (NULLs).
✅ Fix #2: Use IS NULL in the WHERE Clause (Conditional Logic)
If your goal is to check for unmatched rows specifically, use IS NULL
:
sqlCopyEditSELECT *
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
WHERE d.id IS NULL;
🟡 This returns only employees with no matching department — often used for “find orphans” type queries.
✅ Fix #3: Use CASE WHEN for Conditional Filtering
If you want a more nuanced condition (e.g. show all employees, but flag those in Sales), try this:
sqlCopyEditSELECT e.name,
CASE WHEN d.name = 'Sales' THEN 'Sales' ELSE 'Other/None' END AS dept_category
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
✅ Keeps all employees, gives you a flag you can use later.
Behavior Across SQL Dialects
Database | Behavior of LEFT JOIN + WHERE on right table | Notes |
---|---|---|
PostgreSQL | Strict — behaves like INNER JOIN | You must filter right-table columns inside JOIN to preserve NULLs |
MySQL | Same as Postgres | Right-table filters in WHERE clause remove unmatched rows |
SQL Server | Same — WHERE acts after JOIN | Use OUTER APPLY for more advanced conditional joins |
Oracle | Same behavior; legacy syntax uses (+)= | ANSI JOINs recommended for modern, predictable behavior |
Hive SQL | Behaves like INNER JOIN when filtering in WHERE | Join optimization may push predicates down — always put filters in ON clause for LEFT JOIN correctness |
Summary
Query Pattern | Result Type |
---|---|
LEFT JOIN + WHERE on left table only | Preserves LEFT JOIN |
LEFT JOIN + WHERE on right table | Behaves like INNER JOIN |
LEFT JOIN + ON with right-table filter | Preserves unmatched rows |
LEFT JOIN + CASE WHEN | Useful for conditional logic |
🧭 Final Thoughts
SQL behavior is all about order of operations:
- Joins happen first.
WHERE
filters apply next.- So if your
WHERE
clause filters out NULLs, you lose unmatched rows.
Always ask yourself: Do I want unmatched rows to appear? If yes, make sure right-side filters go into the JOIN
clause — not WHERE
.
📌 Pro Tip
You can test if a LEFT JOIN
is still “lefty” by temporarily adding:
sqlCopyEditWHERE d.id IS NULL
If the result is empty, your join likely became an INNER JOIN
due to misplaced filtering.
Discover more from Daily BI Talks
Subscribe to get the latest posts sent to your email.