SQL uses three-valued logic: a condition can be true, false or unknown. Any comparison with NULL gives NULL, which means unknown. WHERE keeps only the rows where the condition is true.
WHERE col = NULL: this never matches any row. Use IS NULL or IS NOT NULL instead.
COUNT(*) vs COUNT(col): COUNT(*) counts all rows. COUNT(col) counts only the rows where col is not NULL.
Aggregates: SUM, AVG, MIN and MAX ignore NULLs.
AVGdivides by the count of non-null values, not by all rows.SUMover zero rows returns NULL, not 0. Wrap it inCOALESCE(SUM(x), 0)to get 0.COALESCEreturns its first non-null argument.
<> filters: WHERE status <> 'archived' also drops the rows where status is NULL. This surprises many people. IS DISTINCT FROM treats NULL as an ordinary value when it compares, so use status IS DISTINCT FROM 'archived' to keep them.
NOT IN: if the list contains a NULL, NOT IN returns no rows.
- A comparison with NULL is unknown, so use IS NULL instead of = NULL.
- COUNT(*) counts all rows, while COUNT(col) skips NULLs.
- Aggregates ignore NULLs, and SUM over no rows returns NULL.
- The <> and NOT IN filters silently remove rows with NULL.
- IS DISTINCT FROM and COALESCE are the tools to handle NULLs safely.