Visual walkthrough — Aggregate functions — COUNT, SUM, AVG, MIN, MAX
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".

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.

- — 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.

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.

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.

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.

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.

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.

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 →