Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
GROUP BY collapses multiple rows into one row per group. HAVING filters those groups after aggregation. Together they’re how MySQL handles aggregation queries — counting orders per customer, summing revenue by month, finding categories with more than 10 items.

GROUP BY Basics

Common aggregate functions:

HAVING vs WHERE

WHERE filters rows before grouping. HAVING filters groups after aggregation.
HAVING COUNT(*) >= 5 works because COUNT(*) is available after aggregation. WHERE COUNT(*) >= 5 is a syntax error — COUNT() doesn’t exist at the WHERE stage. Use both together: WHERE to reduce rows before the grouping (faster — can use indexes), HAVING to filter the resulting groups.

ONLY_FULL_GROUP_BY

MySQL enforces ONLY_FULL_GROUP_BY by default. Every column in the SELECT list must either appear in the GROUP BY clause or be wrapped in an aggregate function:
ANY_VALUE() explicitly tells MySQL “I know this column isn’t in GROUP BY, pick any value from the group.” Use it only when the column is functionally dependent on the GROUP BY column (all rows in the group have the same value).

GROUP BY WITH ROLLUP

WITH ROLLUP adds subtotal rows after each group level and a grand total at the end:
MySQL adds NULL for the grouped columns in subtotal and grand total rows. COALESCE replaces those NULLs with readable labels. WITH ROLLUP is useful for summary reports where you want group subtotals without running multiple queries. For more complex analytics — running totals, rankings, moving averages — window functions handle cases that GROUP BY can’t. See Window Functions in MySQL 8.4.

Frequently Asked Questions

Can I use a column alias in HAVING?

Yes, in MySQL — unlike most other databases. MySQL resolves column aliases from SELECT before evaluating HAVING:
This is a MySQL-specific extension to the SQL standard. It’s convenient but not portable.

Why does GROUP BY without ORDER BY return rows in random order?

Because MySQL doesn’t guarantee order without an explicit ORDER BY. Even if the output looks ordered today, it can change with a different query plan or data distribution. Always add ORDER BY when order matters.

What’s the difference between COUNT(*) and COUNT(col)?

COUNT(*) counts all rows including NULLs. COUNT(col) counts only rows where col is not NULL. COUNT(DISTINCT col) counts unique non-NULL values. Use COUNT(*) for row counts and COUNT(col) when you specifically want to count non-NULL occurrences.

Troubleshooting

See also