Filtering groups with HAVING

WHERE filters rows before grouping. HAVING filters the groups afterwards.

Beginner6 min readLesson 13 of 38
In this lesson 3 sections
  1. Which one do I use?
  2. Using the alias in HAVING
  3. Snowflake notes
Jump to practice

You want reps with at least five won deals. This fails:

SELECT owner_id, COUNT(*) AS won_deals
FROM opportunities
WHERE is_won
  AND COUNT(*) >= 5     -- error: no groups exist yet
GROUP BY owner_id;

WHERE runs before GROUP BY, so there are no counts to check yet. HAVING runs after grouping:

SELECT owner_id, COUNT(*) AS won_deals
FROM opportunities
WHERE is_won                 -- 1. keep only won deals
GROUP BY owner_id            -- 2. one pile per rep
HAVING COUNT(*) >= 5;        -- 3. keep reps with 5 or more

Which one do I use?

Ask yourself Use
Does the condition look at one row? (is_won, amount > 1000) WHERE
Does it look at a group’s total? (COUNT(*) >= 5, SUM(amount) > 100000) HAVING

Put row filters in WHERE even when the query also has HAVING. Throwing rows away early is faster, and the intent is clearer.

Using the alias in HAVING

Most databases make you repeat the aggregate in HAVING:

HAVING COUNT(*) >= 5          -- works everywhere
HAVING won_deals >= 5         -- MySQL and SQLite only

HAVING runs before SELECT (lesson 3), so the alias doesn’t technically exist yet. Repeat the aggregate to stay portable.

Snowflake notes

Snowflake also lets you use the SELECT alias in HAVING, so HAVING won_deals >= 5 works there. Repeat the aggregate anyway if the query might move to SQL Server or PostgreSQL.

Try it

Show each lead source whose open pipeline (the total amount of open deals) is over $250,000, largest first. Return lead_source and the total.

Write it in the sandbox below, then press Check answer.

SQL sandbox

Run queries against the sample CRM tables (accounts, opportunities,users, contacts and opportunity_history), right in your browser. Nothing you run leaves your computer.

Back to the top of the lesson