Module 2: Aggregating & Grouping Data25 min

Filtering Groups with HAVING

Progress Tracking

Log in to save this lesson and continue from where you left off.

Log in

The WHERE Problem

You know WHERE filters rows. But what if you want to filter based on an aggregate result? “Show me departments with more than 3 employees” or “Find customers who’ve spent over $1000 total.”

WHERE can’t do this. It runs before GROUP BY, so the aggregates don’t exist yet:

SQL
-- This fails: aggregates aren’t allowed in WHERE
SELECT
  department,
  COUNT(*) AS emp_count
FROM techcorp_workforce
WHERE COUNT(*) > 3
GROUP BY department;

SQL will reject this query. WHERE runs before any grouping happens, so COUNT(*) doesn’t exist yet.

HAVING: The Solution

HAVING is like WHERE, but for aggregated results. It runs after GROUP BY:

SQL
-- This works
SELECT
  department,
  COUNT(*) AS emp_count
FROM techcorp_workforce
GROUP BY department
HAVING COUNT(*) > 3;

Now you only see departments with more than 3 employees. The filtering happens after the counting.

WHERE vs HAVING

Here’s the key distinction:

  • WHERE filters individual rows before grouping
  • HAVING filters groups after aggregation

You can use both in the same query, and often should.

Table: techcorp_workforce
idfirst_namelast_namedepartmentsalaryphone_numberjoining_date
1SarahMitchellHR95000555-01012021-03-15
2MichaelChenHR88000555-01022022-06-01
3EmilyRodriguezHR825002021-09-20
4DavidParkHR80000555-01042023-01-10
5LisaThompsonHR650002021-04-05
1
Combine WHERE and HAVING

Add a HAVING clause to keep only departments where the average salary exceeds 80000 for recent hires (after 2022). The WHERE already filters to recent hires.

Tables: techcorp_workforce

This finds departments where recent hires have an average salary over 80K. WHERE narrows down who’s included, HAVING narrows down which groups you see.

The Execution Order

Understanding when each clause runs helps clarify this:

  1. FROM: Get the table
  2. WHERE: Filter individual rows
  3. GROUP BY: Create groups from remaining rows
  4. HAVING: Filter groups
  5. SELECT: Calculate and choose columns
  6. ORDER BY: Sort the results
Common interview question

“What’s the difference between WHERE and HAVING?” Now you know.

The Alias Trap

A common gotcha: in most SQL dialects, you can’t use column aliases in HAVING.

SQL
-- This fails in most databases
SELECT
  department,
  COUNT(*) AS emp_count
FROM techcorp_workforce
GROUP BY department
HAVING emp_count > 3; -- Can’t use alias here!

-- Repeat the aggregate instead
SELECT
  department,
  COUNT(*) AS emp_count
FROM techcorp_workforce
GROUP BY department
HAVING COUNT(*) > 3; -- Use the actual function

Why? Because HAVING runs before SELECT, so the alias doesn’t exist yet. You have to repeat the aggregate function.

MySQL exception

MySQL allows aliases in HAVING. But for portability, always use the full aggregate expression.

Table: facebook_web_log
user_idtimestampaction
02019-04-25 13:30:15page_load
02019-04-25 13:30:18page_load
02019-04-25 13:30:40scroll_down
02019-04-25 13:30:45scroll_up
02019-04-25 13:31:10scroll_down
2
Find Repeated Actions

Find all actions that occurred more than once using GROUP BY and HAVING.

Tables: facebook_web_log

Common HAVING Patterns

Minimum Threshold

SQL
-- Only show categories with significant volume
SELECT
  category,
  COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) >= 10;

Multiple Conditions

You can combine conditions in HAVING with AND, just like WHERE.

3
Multiple HAVING Conditions

Find departments with more than 2 employees and an average salary over 75000.

Tables: techcorp_workforce

Departments With 5 Employees

Use HAVING COUNT(*) to filter groups by member count.

Table: employee
idfirst_namelast_nameagesexemployee_titledepartmentsalarytargetbonusemailcityaddressmanager_id
5MaxGeorge26MSalesSales1300200150Max@company.comCalifornia2638 Richards Avenue1
13KattyBond56FManagerManagement1500000300Katty@company.comArizona1
11RicherdGear57MManagerManagement2500000300Richerd@company.comAlabama1
10JenniferDion34FSalesSales1000200150Jennifer@company.comAlabama13
19GeorgeJoe50MManagerManagement1000000300George@company.comFlorida1003 Wyatt Street1
4
Departments With 5 Employees
View solution

Find departments with at more than or equal 5 employees.

Tables: employee

Key Takeaways

  • WHERE filters rows before GROUP BY
  • HAVING filters groups after aggregation
  • HAVING can use aggregate functions; WHERE cannot
  • You can’t use column aliases in HAVING (in most dialects)
  • Use both together: WHERE for row-level, HAVING for group-level filtering
  • Remember the execution order: FROMWHEREGROUP BYHAVINGSELECTORDER BY

What’s Next

You’ve seen that aggregates ignore NULLs. But there’s more nuance here than you might expect, and getting it wrong leads to incorrect results. Next, we’ll dig into exactly how NULLs behave in aggregations.