GROUP BY groups rows with the same values so aggregates apply per group. HAVING filters groups after aggregation. Example: find departments with more than 5 employees using GROUP BY and HAVING COUNT(*) > 5.
Grouping Results
-- Sales per city
SELECT city, COUNT(*) AS orders
FROM orders
GROUP BY city;
-- Only cities with 10+ orders
SELECT city, COUNT(*) AS orders
FROM orders
GROUP BY city
HAVING COUNT(*) >= 10
ORDER BY orders DESC;
WHERE filters rows before grouping. HAVING filters after.