The HAVING clause filters groups after they've already been aggregated by GROUP BY — it's the version of WHERE that works on the results of SUM(), COUNT(), AVG(), and the other aggregate functions, rather than on individual rows.

This distinction trips up a lot of people learning SQL, because the two clauses look similar but run at completely different stages of a query.

Why WHERE Can't Filter Aggregates

WHERE runs before grouping, filtering raw rows out of the table one at a time — at that point, no aggregate value exists yet to compare against. This query is invalid in every major database:

-- Invalid: SUM(salary) doesn't exist yet when WHERE runs
SELECT department, SUM(salary) AS total_salary
FROM employees
WHERE SUM(salary) > 150000
GROUP BY department;

HAVING: Filtering After Aggregation

HAVING runs after the groups and their aggregate values have been computed, so it can reference them directly:

SELECT department, SUM(salary) AS total_salary
FROM employees
GROUP BY department
HAVING SUM(salary) > 150000;

Result:

department | total_salary
-------------|-------------
Engineering | 278000
Sales | 191000

Marketing's total of 115,000 falls below the threshold, so that entire group is dropped from the result — not individual employees, but the whole department bucket.

Combining WHERE and HAVING

You can use both in the same query — WHERE trims rows before grouping, HAVING trims groups after:

SELECT department, COUNT(*) AS headcount
FROM employees
WHERE hire_date >= '2019-01-01'
GROUP BY department
HAVING COUNT(*) > 1;

Result:

department | headcount
-------------|----------
Engineering | 3
Marketing | 2

Here, the WHERE clause excludes anyone hired before 2019 first; then departments are grouped and counted; then any department left with only one person is dropped by HAVING. Sales, which had just one qualifying employee after the date filter, disappears from the final result.

Quick Rule of Thumb

If the condition involves a raw column value, use WHERE. If it involves the result of an aggregate function, use HAVING. Getting this right is really just about remembering the order SQL actually executes a query in: filter rows, then group them, then filter groups.