SelectFromData
AnalyticsLesson 5 of 9 · 20 min

HAVING and the Order SQL Actually Runs In

You can now group and aggregate. The next question is inevitable: "show me only the departments with more than one person", a filter on an aggregated value. That needs HAVING, not WHERE. Understanding why means understanding the order SQL really executes in.

In one line

WHERE filters rows before grouping; HAVING filters groups after aggregating. You can't put COUNT(*) in WHERE because the count doesn't exist yet at that stage.

The order SQL runs (not the order you write it)

You write SELECT first, but the database runs the clauses in a different order. This single diagram explains most "why can't I do that here?" errors:

Logical execution order

  1. 1FROM

    Pick the table(s).

  2. 2WHERE

    Filter individual rows.

  3. 3GROUP BY

    Collapse rows into groups.

  4. 4HAVING

    Filter the groups.

  5. 5SELECT

    Choose columns / aggregates.

  6. 6ORDER BY

    Sort the final result.

WHERE happens at step 2, before groups exist. HAVING happens at step 4, after the aggregates are computed. That's the whole reason they're two different keywords.

HAVING — filter the groups

HAVING runs after GROUP BY, so it filters the already-aggregated rows. Drop any group with a single member and only the two-person departments remain:

departmentheadcount
Engineering2
Sales2
Marketing1
HAVING COUNT(*) > 1
departmentheadcount
Engineering2
Sales2
HAVING tests the aggregate — Marketing has headcount 1, so the whole group is dropped. (WHERE could not do this; the count doesn't exist yet.)

Try it now: write the query the visual showed, the departments with more than one employee (department and headcount).

Your turn · Exercise

Return the departments that have more than one employee, with columns department and headcount.

The data

employees

5 rows returned

namedepartmentsalary
AdaEngineering62,000
BoSales48,000
CyEngineering71,000
DiSales52,000
EliMarketing45,000
Runs in your browser

Run it: departments with more than one employee.

HAVING filters on the aggregate; WHERE could not do this
Editable · runs in your browser

Marketing (one person) is filtered out by HAVING. Try changing it to HAVING AVG(salary) > 50000 to filter on a different aggregate.

WHERE and HAVING in the same query

They're not either/or; you often want both. WHERE trims rows before grouping (cheaper, and changes what's counted); HAVING trims groups after. Run it: among non-Marketing staff, departments whose average salary tops €50k.

WHERE filters rows first, then HAVING filters the resulting groups
Editable · runs in your browser

Watch out

Putting an aggregate in WHERE, like WHERE COUNT(*) > 1, is the classic beginner error and it errors out every time. If you're filtering on something you had to COUNT, SUM, or AVG to know, it belongs in HAVING.

From practice

Rule of thumb: filter as much as you can in WHERE (it runs first, on fewer rows, and is usually faster), and reserve HAVING strictly for conditions on the aggregated result.

Your turn

Filter on an aggregate. Return the departments whose average salary is above 50000 (department and avg_salary).

Your turn · Exercise

Return the departments whose average salary is above 50000, with columns department and avg_salary.

The data

employees

5 rows returned

namedepartmentsalary
AdaEngineering62,000
BoSales48,000
CyEngineering71,000
DiSales52,000
EliMarketing45,000
Runs in your browser

Next: the last fundamental, combining two tables with a JOIN, and the row beginners always lose.