Collapsing rows into summaries — and why WHERE and HAVING are not interchangeable
Almost every business question involves aggregation: totals, averages, counts by category, top-N per group. GROUP BY is the mechanism, but the WHERE vs HAVING distinction — filtering before vs after aggregation — is where most candidates stumble. Getting it wrong produces results that look right but calculate incorrectly.
GROUP BY collapses all rows that share the same value(s) in the specified columns into a single output row. After grouping, you can only reference columns that are either in the GROUP BY list or wrapped in an aggregate function — because individual row values no longer exist, only group-level summaries.
WHERE filters individual rows before grouping. HAVING filters groups after aggregation. Rule of thumb: if the condition references an aggregate (COUNT, SUM, etc.), it belongs in HAVING.
COUNT(*) vs COUNT(column) is a classic gotcha. If a column has NULLs, the counts will differ — COUNT(column) silently skips them.