What is the difference between GROUP BY and HAVING clauses?
WHERE filters rows. HAVING filters groups. That's the core difference.
29 Apr 2024

WHERE filters rows. HAVING filters groups. That's the core difference.
I've seen developers use WHERE when they need HAVING (and vice versa) because the distinction isn't obvious at first. Here's how to think about it.
GROUP BY: create the groups
GROUP BY takes all rows with the same value in a column and collapses them into one group. You then apply aggregate functions (SUM, COUNT, AVG) to each group.
SELECT product_category, SUM(total_sales) AS total_sales
FROM orders
GROUP BY product_category
This returns one row per category with the total sales for each. Every row in orders gets assigned to a group based on its product_category value.
HAVING: filter after grouping
HAVING filters groups after GROUP BY has run. It works on aggregate values.
SELECT product_category, SUM(total_sales) AS total_sales
FROM orders
GROUP BY product_category
HAVING SUM(total_sales) > 1000
This only returns categories where total sales exceed 1000. You can't do this with WHERE because WHERE runs before grouping -- it doesn't know what the sum will be yet.
The execution order
This is the key insight:
FROM-- Pick the tableWHERE-- Filter individual rowsGROUP BY-- Create groups from remaining rows- Aggregate functions run (
SUM,COUNT, etc.) HAVING-- Filter groups based on aggregate resultsSELECT-- Choose which columns to returnORDER BY-- Sort the output
WHERE and HAVING both filter. But they operate at different stages. WHERE sees individual rows. HAVING sees grouped results.
When to use which
- Use WHERE when filtering on raw column values:
WHERE status = 'active' - Use HAVING when filtering on aggregate results:
HAVING COUNT(*) > 5 - Use both when you need to filter rows first, then filter groups: "Show categories with total sales over $1000, but only count orders from 2024."
SELECT product_category, SUM(total_sales) AS total_sales
FROM orders
WHERE order_year = 2024
GROUP BY product_category
HAVING SUM(total_sales) > 1000
WHERE narrows the rows to 2024. GROUP BY groups by category. HAVING keeps only the high-revenue categories. Each clause does its job at the right stage.