4.4.8 · D2Databases

Visual walkthrough — Aggregate functions — COUNT, SUM, AVG, MIN, MAX

1,956 words9 min readBack to topic

We use the parent's tiny table the whole way so every number is checkable:

id dept salary
1 A 100
2 A 200
3 B 300
4 B NULL

Step 1 — A column is just a vertical list of boxes

WHAT. Before any function runs, picture the salary column alone: four stacked boxes. Three hold numbers (); one is a shaded "unknown" box (NULL).

WHY start here. Every aggregate — COUNT, SUM, AVG, MIN, MAX — eats exactly this list and spits out exactly one value. If you can see the list clearly, you can see what each function does to it. Nothing else is needed yet: no formulas, no symbols.

PICTURE. Look at the four boxes. The blue ones are real numbers. The red hatched box is NULL — it means "we genuinely do not know this value", not "zero".

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX

Step 2 — COUNT is a walk that tallies boxes

WHAT. Walk down the column top to bottom. Keep a tally . Two different rules give two different tallies:

  • — tick every box you pass: .
  • — tick only boxes that hold a real value: (skip the shaded one).

WHY two rules. A row existing is a fact even if its cell is empty — so counts the desk whether or not a student sits there. But asks "how many known salaries?", so it must skip the unknown.

PICTURE. The green arrow is the walk. Above each box sits the running tally after visiting it. Watch the two counters diverge at box 4: the star-counter climbs to 4, the salary-counter stays at 3.

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX
  • — the means "the whole row", so NULL never matters.
  • — names a column, so it obeys the skip-NULL rule.

Step 3 — SUM is the same walk, but we add instead of tally

WHAT. Walk again. This time keep a running total , starting at . At each box: if it holds a number, add it to ; if it's the shaded NULL box, add nothing and move on.

WHY skip, not add-zero. Adding NULL would be claiming "the unknown salary is 0" — a lie about the data. SQL refuses to invent that, so the total stays at . (If you genuinely want NULL-as-zero, that's a choice you make with COALESCE and NULLIF, not a default.)

PICTURE. The yellow bar grows step by step. At box 4 the bar does not grow — the shaded box contributes a flat segment.

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX

Step 4 — AVG is not a new walk: it reuses and

WHAT. Here is the central result. The database already carried two pencilled numbers down the column: the sum and the count of values it actually added. The average is just their ratio.

  • — "add up through ", which is exactly the total we built in Step 3.
  • — the count of non-NULL values, exactly from Step 2. Not .
  • The fraction bar — "fair share": split the total equally among the boxes that had a value.

WHY and not . The NULL box never contributed to (Step 3), so it has no right to a share of it. Dividing by 4 would spread across a box that put in nothing — deflating the true average. So:

PICTURE. Left: the yellow bar. Below: the three blue boxes it came from. The red "phantom" box shows the wrong denominator (4) and the wrong answer (150) crossed out.

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX

Step 5 — MIN and MAX: the same walk, keeping the champion

WHAT. One more pass. Keep one box in your hand — the current champion. For MIN, swap whenever you see something smaller; for MAX, whenever you see something bigger. Skip the shaded NULL (an unknown can't win or lose a comparison).

WHY skip NULL again. "Is unknown 100?" has no honest answer, so NULL never becomes the champion and never displaces one. The winner is chosen only among known values.

PICTURE. The number line holds the three known salaries. A green marker slides to the leftmost (MIN) and a red marker to the rightmost (MAX); the NULL floats off the line, disconnected.

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX

Step 6 — The degenerate case: an all-NULL (or empty) column

WHAT. Suppose every box in the column is shaded — dept B, if it had only row 4. Now walk each function:

Function Walk result Returns
one box exists
zero known values
nothing added NULL
— undefined NULL
no champion ever chosen NULL

WHY SUM returns NULL, not 0. This surprises everyone. But look at the walk: SUM had no non-NULL rows to add. It never even started adding. Returning would claim "the total of these unknowns is zero" — again inventing data. And AVG would need , which is undefined, so it too yields NULL rather than dividing by zero.

PICTURE. Three shaded boxes feeding into a decision fork: COUNT paths land on a number, SUM/AVG/MIN/MAX paths all land on a NULL bubble.

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX

Step 7 — GROUP BY: run the whole walk once per mini-table

WHAT. GROUP BY dept doesn't change any function — it just slices the column into stacks before the walk. Dept A = boxes ; dept B = boxes . Then Steps 2–5 run inside each stack.

WHY B's average is 300, not 150. Inside stack B the walk finds one known value () and one NULL. So and — dividing by 1, not 2. Same skip-NULL rule as Step 4, applied per group.

PICTURE. The column splits into two shorter columns; each runs its own tiny SUM/COUNT/AVG, producing one summary row apiece.

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX
Recall Why HAVING and not WHERE, in one line

The average doesn't exist until after the per-group walk finishes. WHERE runs before grouping (Step 7 hasn't happened yet), so it can't see ; HAVING runs after, so it can. Full order in SQL query execution order. Details in GROUP BY and HAVING.


The one-picture summary

WHAT. One column, one walk, five pencils. Every aggregate is the same downward pass keeping a different scrap of paper: a tally (COUNT), a running total (SUM), the pair -and- (AVG), or the current champion (MIN/MAX) — and every one of them steps over the shaded NULL box, except which counts the box itself.

Figure — Aggregate functions — COUNT, SUM, AVG, MIN, MAX
Recall Feynman: tell it to a 12-year-old

Imagine a stack of index cards, one salary per card, and one card left blank because we never found out that person's pay. You walk down the stack once, top to bottom, holding a few scraps of paper. On one scrap you make a tick for every card — that's , and even the blank card gets a tick because a card is there. On another scrap you tick only cards with a number — that's , and the blank card gets skipped. On a third scrap you keep a running sum, adding each number but adding nothing for the blank — that's SUM. The average? You don't do a new walk: you just take your running sum and split it among the cards that actually had a number — three cards, so , not . The blank card never put money in, so it gets no share. And if every card were blank, you'd have nothing to add and nothing to split, so SUM and AVG shrug and say "unknown" (NULL) — only the tick-counter can still say "zero".


Connections

  • NULL handling in SQL — the shaded box that drives every "3 not 4".
  • GROUP BY and HAVING — Step 7: run the same walk per mini-table.
  • COALESCE and NULLIF — force NULL-as-0 before SUM/AVG.
  • SQL query execution order — why WHERE can't see the average but HAVING can.
  • Window functions — the same walk that doesn't collapse the rows.
  • DISTINCT keyword — de-dupe the column before COUNT.
  • Yeh Hinglish mein padho →