Per-bucket summaries
GROUP BY partitions rows into buckets by one or more columns; aggregates then run per bucket. HAVING filters the buckets after aggregation (in contrast to WHERE, which filters rows before).
The mental order of operations:
FROM+ joins assemble candidate rows.WHEREfilters those rows.GROUP BYpartitions surviving rows into buckets.- Aggregates compute one value per bucket.
HAVINGfilters buckets.SELECTprojects the columns/expressions you want.ORDER BY+LIMITfinalize.
Warning: Postgres errors out if you GROUP BY a subset of selected columns. SQLite lets it through and picks an arbitrary value for the unaggregated columns — same trap as MySQL pre-5.7. Always include every non-aggregated SELECT column in GROUP BY, or wrap it in an aggregate.