SQL WHERE vs HAVING

They look similar and both filter — but they operate at completely different stages of the query, and mixing up when to use which is one of the most common intermediate-level mistakes.

What & Why

Both WHERE and HAVING remove rows from a result — that's what makes them easy to confuse. The difference is entirely about when each one runs, relative to GROUP BY:

A useful way to remember it: if the condition needs an aggregate function (COUNT, SUM, AVG, and so on) to even make sense, it belongs in HAVING. If it's checking a plain column value on an individual row, it belongs in WHERE. A single query can absolutely use both together — and when it does, the order they run in genuinely changes the result, not just the syntax.

See How It Works

BUSINESS QUESTION

Marketing wants channels with at least two campaigns whose spend is 50,000 or more.

idnamechannelspendstart_dateend_datestatustarget_segment
1Spring Launchgoogle_ads55000.002024-01-152024-03-31activesmb
2Retention Webinaremail45000.002024-02-102024-04-15activeenterprise
3Finance Retargetinglinkedin50000.002024-03-122024-05-31activeenterprise
4Enterprise Searchgoogle_ads60000.002024-04-012024-06-30activeenterprise
Watch WHERE filter rows before HAVING filters groupsStage 1 of 4
-- start with every marketing.campaigns row
idnamechannelspendWHERE result
1Spring Launchgoogle_ads55,000WAIT
2Retention Webinaremail45,000WAIT
3Finance Retargetinglinkedin50,000WAIT
4Enterprise Searchgoogle_ads60,000WAIT

WHERE and HAVING have not run yet; all four campaigns are source rows.

EXAMPLE QUERY
SELECT
  channel,
  COUNT(*) AS qualifying_campaigns
FROM marketing.campaigns
WHERE spend >= 50000
GROUP BY channel
HAVING COUNT(*) >= 2
ORDER BY qualifying_campaigns DESC, channel;
Now You Try

Practice this concept

Marketing wants channels with at least two campaigns whose spend is 50,000 or more.

Available schema
marketing

Prefix tables with marketing.table_name.

channelqualifying_campaigns
marketing.campaigns
ColumnType
idinteger
nametext
channeltext
spendnumeric
start_datedate
end_datedate
statustext
target_segmenttext
legacy_idtext
query.sql
Intermediate business practice

Sign up free to try it on a real business scenario