GROUP BY collapses rows that share the same value(s) into groups so aggregates can be computed per group. HAVING filters those groups by an aggregate condition — like WHERE, but applied after grouping.
GROUP BY is how you go from a single overall total to a total per category: orders per user, revenue per country, count per status. Every column in the SELECT that isn't inside an aggregate must appear in GROUP BY (this rule catches many beginners). The distinction between WHERE and HAVING is the classic interview point: WHERE filters individual rows BEFORE grouping, while HAVING filters GROUPS AFTER aggregation. So 'orders over $100' is WHERE, but 'users who placed more than 5 orders' is HAVING COUNT(*) > 5, because that condition is about the group's aggregate, not any single row. Getting this wrong — trying to filter an aggregate in WHERE — produces an error, and trying to filter a raw row in HAVING is inefficient or wrong. For testers, GROUP BY is essential for validating per-category expectations and for finding anomalies like duplicate keys (COUNT(*) > 1 per group).
To verify a promotion limited each customer to 3 orders, a tester runs SELECT user_id, COUNT(*) FROM orders WHERE promo = 'SUMMER' GROUP BY user_id HAVING COUNT(*) > 3 — any returned row is a customer who exceeded the limit, i.e. a bug.
-- Orders and revenue per country (row filter first, then group)
SELECT u.country, COUNT(*) AS orders, SUM(o.total) AS revenue
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'confirmed' -- WHERE: filters rows before grouping
GROUP BY u.country
HAVING SUM(o.total) > 1000 -- HAVING: filters groups after aggregation
ORDER BY revenue DESC;
-- Find customers who placed more than 5 orders
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 5;Scenario: A tester wants 'users who have placed more than 5 orders' and writes SELECT user_id FROM orders WHERE COUNT(*) > 5 GROUP BY user_id. The database throws an error. Explain the mistake and give the correct query, including how WHERE and HAVING differ.