SQL90 min total · 18 parts
SQL Fundamentals: Joins, NULL, Aggregate Functions, and Subqueries
Part 18 of 18 · ~3 min
Common Mistakes Worth Remembering
Every one of these showed up somewhere in the Monday report above, in one form or another:
- Reaching for
= NULLwhen you meanIS NULL. There's no crash to warn you — the query runs, comes back empty every single time regardless of what's actually in the table, and nothing in the output hints that you asked the wrong question. - Treating a missing value as a zero inside
AVG()orSUM(). Both functions skip the row entirely instead, which forAVG()specifically means the denominator shrinks along with the numerator — the average of what's known, not the average with gaps padded to zero. - Trusting
NOT INnext to a subquery column that isn't guaranteedNULL-free. One stray unknown value in that list is enough to empty the whole result for every outer row, the way IT's roster silently zeroed out the Sales pay comparison the moment Dave's row got included.NOT EXISTSdoesn't have this failure mode; make it the default rather than something to remember case by case. - Picking
INNER JOINfor a report that's supposed to show every row regardless of a match — or, less often but just as real, pickingLEFT JOINfor a query that actually needed unmatched rows filtered out. Either direction quietly changes which rows make it onto the slide. - Mixing up
COUNT(*)andCOUNT(column). They agree everywhere the column has no gaps and diverge exactly where it does — which is precisely the case a report can least afford to get wrong, since that's the case with something actually worth reporting on. - Assuming a negated condition catches whatever the original condition missed. Once a
NULLis involved, that assumption breaks specifically because negatingUNKNOWNdoesn't produceTRUE— both the original filter and its negation can, and did, exclude the exact same row. - Building a CTE's aggregation from the table that's allowed to have gaps, instead of joining the complete table in first. It's the difference between a
0you can stand behind and aNULLthat only means the row was never in the running to begin with. - Separating tables with a comma in
FROMand letting the condition that was meant to relate them slip your mind. Nothing crashes — you just get every possible pairing back, a wrong answer wearing a plausible row count.
None of this sticks from reading alone nearly as well as it does from breaking it yourself. Pull up the code lab, paste in the schema from the top of this page, and run each of these bugs on purpose — swap IS NULL for = NULL and watch a filter go silent, put NULL in a NOT IN list and watch a whole result set vanish, wrap a filter in NOT and watch it not do what you expected. Three-valued logic stops being an abstract table of TRUE/FALSE/UNKNOWN the moment you've personally watched it eat a query's output.
And when you're ready to see these same two tables under genuinely different pressure, Understanding SQL Indexes and Query Performance picks up right where this leaves off — same Employees, same Departments, scaled to 2.4 million rows, where getting a query correct stops being the only problem and getting it fast joins the list.
Practice this
Code Lab
Continue learning