Skip to content
Foundations

Grouping, and the two ways to count

COUNT(*) counts rows. COUNT(col) counts values. They are different numbers, and knowing which one you asked for is most of the skill.

12 min · runs against the shop dataset

GROUP BY collapses many rows into one per distinct key, and every column in the select list then has to be either one of those keys or something aggregated across the group. The error you get for breaking that rule is the most useful message in the language, because it tells you both fixes at once.

run it

citytextordersbigintpricedbigintspentnumeric(10,2)
osaka191919330.56
lagos111011034.73
perth131110679.97
cairo101010415.33
quito989719.07

5 rows · 114 examined · 2 pages read

Two counts, two answers

count(*) counts rows in the group. count(total) counts rows where total is not null. On a column with no nulls they agree, which is exactly why the difference goes unnoticed until the day it matters.

The empty group

Over no rows at all, count returns zero and every other aggregate returns null. That asymmetry is deliberate and it bites: a sum of nothing is not 0, and adding it to something else makes the whole expression null.

Zero, and then three nulls.

countedbigintsummednumeric(10,2)averagednumeric
0nullnull

1 row · 90 examined · 1 page read

WHERE against HAVING

WHERE filters rows before grouping. HAVING filters groups after. That is the whole distinction, and it has a cost consequence: a condition that could have run in WHERE and was written in HAVING grouped rows it was always going to throw away.

WHERE picks the rows, HAVING picks the groups.

citytextordersbigint
osaka15
lima11
perth8
lagos7
cairo6

5 rows · 114 examined · 2 pages read

Did it land?

  • How many, and how many priced

    For each customer id that has orders, return the id, how many orders they placed, and how many of those have a total. Call the columns customer_id, placed and priced. Order by customer_id.

  • Only the busy cities

    Return each city with more than five shipped orders, and that count, busiest first then by city. Call the count shipped_orders.