Each group gets its own aggregate NULL facts.

highlighted = computed this step

Grouped aggregate facts

GROUP BY forms buckets, then each bucket gets its own COUNT and SUM facts.

per group facts\text{per group facts}

Read the buckets

The compiler forms 3 groups and computes 3 aggregate facts per group.

groups=3\text{groups}=3

Aggregate NULL examples are tiny finite table transforms; SQL dialect completeness, optimizer behavior, indexing, product behavior, and performance claims are out of scope.

Grouped aggregate facts: base rowsidregionqtynote1east2ok2eastNULLmissing3west5ok4westNULLNULL5northNULLmissing6northNULLNULL Group bucketskeysourceRowsrowCount[east][0, 1]2[west][2, 3]2[north][4, 5]2 Aggregate resultskeysourceRowsaggregates[east][0, 1]{rows_seen:2, qty_seen:1, qty_sum:2}[west][2, 3]{rows_seen:2, qty_seen:1, qty_sum:5}[north][4, 5]{rows_seen:2, qty_seen:0, qty_sum:NULL} NULL-skip detailskeyaggregateseencounted/addednullSkippedresult[east]rows_seen2202[east]qty_seen2111[east]qty_sum2112[west]rows_seen2202[west]qty_seen2111[west]qty_sum2115[north]rows_seen2202[north]qty_seen2020[north]qty_sum202NULL Output rowsregionrows_seenqty_seenqty_sumeast212west215north20NULL Aggregate NULL factsfactvalueinputRowCount6groupCount3aggregateCount3outputRowCount3countStarCountsEveryRowyescountColumnSkipsNullyessumSkipsNullyesallNullSumGivesNULLgroupOrderfirst_seen_input_orderoutputOrderdeterministic_group_order

Each group is separate

The north group can have present-cell count zero while other groups add integers.

separate buckets\text{separate buckets}

Summary

Aggregate NULL behavior is per bucket.

per bucket\text{per bucket}