What is the difference between WHERE and HAVING, and why does the order in which they run matter?
WHERE filters individual rows before the engine groups them. HAVING filters the groups that come out of the aggregation. The logical order of evaluation is FROM, then WHERE, then GROUP BY, then HAVING, then SELECT, then ORDER BY, then LIMIT — so WHERE prunes input and HAVING judges output.
That order is not cosmetic. A predicate in WHERE removes rows before they are grouped, so it changes the aggregates: a WHERE on amount > 100 changes what COUNT and SUM see. A predicate in HAVING sees the finished aggregate and can only discard whole groups, so it changes which groups survive but never what is inside one.
The other practical difference is what each clause can reference. WHERE works on raw column values of a single row, so it can use an index on that column. HAVING works on aggregate results and aliases, which are computed after the scan, so an index on the underlying column generally cannot help unless the optimizer can prove the equivalence.
Two rules of thumb settle most arguments. If the condition mentions no aggregate and no SELECT alias, it belongs in WHERE. If it mentions COUNT, SUM, AVG, MIN, MAX or an alias, it has to go in HAVING — or, better, be rewritten so the filter can happen in WHERE at all.
There is a common exception worth knowing: WHERE cannot contain a window function, because window functions are evaluated after GROUP BY. A query that filters on a window result — the classic "top row per group" pattern — must use a CTE or a subquery with the window computed first, then filter outside it.