SQL Kitchen

Lesson · Recipe № 007

HAVING

Lesson 7
Recipe

HAVING


After GROUP BY you have one row per tier. Say you only want the tiers whose customers average more than 10 visits. The instinct is WHERE AVG(visits) > 10, but this is not how filtering on groups is done in SQL.

name                 visits  favorite_station  tier
Mrs. Papadaki        42      grill             vip
The Nikolaou family  15      salad             regular
Petros               3       pastry            new
Widow Fotiou         60      grill             vip
Ilias                8       salad             regular
A pair of tourists   1       pastry            new

The reason: WHERE is checked before the rows are grouped, back when they are still individual customers. There is no average of a tier yet, so there is nothing for that condition to test.

HAVING is checked after the grouping and the aggregate. By then every tier has its number, and HAVING keeps or drops whole tiers based on it. It goes right after the GROUP BY.

Query

SELECT tier, AVG(visits)
FROM customers
GROUP BY tier
HAVING AVG(visits) > 10;

↓ result

tier     avg
vip      51
regular  11.5

Here is the difference in one picture. WHERE throws out customers; HAVING throws out tiers. This WHERE drops the most-visited regular first, then averages whatever is left in each tier, so every tier still shows up but its average has moved.

Query

SELECT tier, AVG(visits)
FROM customers
WHERE visits < 50
GROUP BY tier;

↓ result

tier     avg
vip      42
regular  11.5
new      2

Same table, same numbers to start from. The HAVING query above uses every customer and then removes a tier. The WHERE query removes one customer and keeps every tier. Different tool, different result.

Still, a query can use both: WHERE narrows the rows going in, HAVING narrows the groups coming out. The example below shows how it works on Konstantinos' wine list.

name          type     region     price
Assyrtiko     white    Santorini  14
Agiorgitiko   red      Nemea      12
Xinomavro     red      Naoussa    16
Moschofilero  rosé     Mantinia   10
Malagousia    white    Macedonia  11
Mavrodafni    dessert  Patras     9

Query

SELECT type, AVG(price)
FROM wines
WHERE price > 10
GROUP BY type
HAVING AVG(price) > 13;

↓ result

type  avg
red   14

SQL runs the clauses in this order: FROM, then WHERE, then GROUP BY, then HAVING, then SELECT, then ORDER BY. WHERE near the start, before there are any groups. HAVING near the end, once the groups and their numbers exist.

Rule of thumb: if the condition is about one row on its own, it is a WHERE. If it is about a whole group, a COUNT, a SUM, an AVG, it is a HAVING.

On the rail · tonight's orders

ORDER 019

for Giorgos


Giorgos only cares about the stations actually carrying the menu tonight: the ones whose dishes add up to more than 20, nothing under it.

Your query returns

One row per station whose prices total more than 20, with that total.


ORDER 020

for front of house


Front of house wants to know which loyalty tiers earn the name: averaging more than 10 visits.

Your query returns

One row per tier whose average visit count is above 10, with that average.


ORDER 021

for Konstantinos


Konstantinos suspects a few cheap bottles are dragging down the wine list's reputation. He wants to know which types are still expensive once anything under 10 is set aside.

Your query returns

One row per type that, counting only bottles over 10, averages more than 13.