skip to content

Classic relational algebra has no way to compute a count or a sum. What does the extended grouping-and-aggregation operator add, how is its result defined, and why can the five basic operators not express it?

level: middleimportance: should knowfreq 42%

answer

  1. gamma [ group-by ; aggregates ]
  2. basic five are value-preserving; aggregates invent values
  3. one tuple per non-empty group; no groups from no rows
  4. HAVING = sigma applied after gamma
  5. aggregates ignore nulls except COUNT(*)

basics

~20 s

Grouping/aggregation (written gamma) partitions a relation by grouping attributes, applies aggregate functions such as COUNT, SUM, MIN, MAX, AVG to each partition, and returns one tuple per group. The basic operators only select, combine and drop existing tuples and attributes; they can never compute a new value from a set of tuples.

solid answer

~60 s

The extended operator is usually written `gamma[G; F](R)`: `G` is the list of grouping attributes, `F` the list of aggregate expressions. - Tuples of `R` are partitioned into groups that agree on all of `G`. - Each aggregate in `F` is evaluated over its group, consuming the whole group and producing a single scalar. - The result has schema `G` plus one attribute per aggregate, and exactly one tuple per non-empty group. - With `G` empty, the whole relation is one group and the result is a single tuple. The basic five — selection, projection, product, union, difference — are **value-preserving**: every value in a result already appeared in an input. `COUNT` and `SUM` invent values that appear nowhere in the input, so no combination of the five can produce them. Aggregation is therefore a genuine extension, not sugar. Two semantic points interviewers probe: aggregation is defined over **bags**, since `SUM` and `COUNT` depend on duplicates that set-projection would have destroyed; and aggregates skip nulls, except `COUNT(*)`, which counts tuples.

code

text · 4 lines
text
gamma [ dept ; COUNT(*) -> headcount, SUM(salary) -> payroll ] (Emp)

result schema: (dept, headcount, payroll)
one tuple per distinct dept present in Emp

go deeper

for a junior

Say what grouping and aggregation produce — one row per group, with counts or sums — and note the result schema is grouping attributes plus aggregates.

for a middle

Add the expressiveness argument (the basic operators cannot invent new values) and the empty-group and null-handling edges.

for a senior

Explain why aggregation forces bag semantics, that it is a blocking operator producing a set along the grouping key, and which predicates can be pushed below it.

for a principal

Frame aggregation as the boundary where the algebra stops being closed over value-preserving operations, and discuss the consequences for rewrite legality and for pipelined versus blocking execution.

## Where the gap is Codd's basic operators are selection, projection, Cartesian product, union and set difference (with rename added to make the algebra complete relative to tuple calculus). Each of them has a property worth naming explicitly: they are **value-preserving**. Selection keeps a subset of tuples. Projection keeps a subset of attributes. Product pairs tuples. Union and difference add or remove whole tuples. In every case, each value appearing in the output already appeared somewhere in an input. `COUNT(*) = 47` is a value that appears nowhere in the input. So does `SUM(amount) = 19203.55`. No finite composition of value-preserving operators can conjure them. This is not a matter of convenience — it is a strict expressiveness gap, and it is why aggregation is classified as an **extended** operator rather than a derived one. ## The operator The usual notation is ``` gamma [ a, b ; COUNT(*) -> n, SUM(amt) -> total ] (R) ``` read as: group `R` by attributes `a` and `b`; for each group emit the group's `a` and `b`, the number of tuples, and the sum of `amt`. Semantics, step by step: 1. **Partition.** Tuples of `R` are placed in the same group when they agree on every grouping attribute. Grouping uses equality on values, so it also fixes an equivalence relation on the relation. 2. **Aggregate.** Each aggregate function maps a *bag* of values (or of tuples, for `COUNT(*)`) to one scalar. 3. **Emit.** One tuple per group, with schema = grouping attributes + one attribute per aggregate. The result is always a relation, so closure is preserved and gamma can be composed with everything else. ### Degenerate and edge cases - **Empty grouping list.** The entire relation forms one group; the result is a single tuple. Note this differs from a per-group aggregation over an empty relation, which returns zero tuples, whereas whole-relation aggregation over an empty relation returns one tuple containing `COUNT = 0` and typically null for `SUM`, `MIN`, `MAX`, `AVG`. - **Empty input with grouping attributes.** Zero groups, therefore zero tuples. There is no such thing as an "empty group" in the result — groups only exist where tuples exist. This is why aggregation cannot by itself produce zeros for categories that had no rows; you need an outer join against a dimension of all categories first. - **Nulls.** By definition (and in SQL) aggregate functions other than `COUNT(*)` ignore null inputs. `AVG` therefore divides by the count of non-null values, not by the group size, which is the classic source of "my average looks too high" bugs. Grouping, however, treats nulls as *equal to each other* for the purpose of forming groups, which is deliberately inconsistent with the way equality behaves in a predicate. ## Why bag semantics are load-bearing here Aggregation only makes sense over bags. Consider `SUM(amount)` on a relation where two customers happen to have the same amount. Under strict set semantics, projecting to `amount` would collapse the duplicates and the sum would be wrong. Any algebra that includes aggregation must therefore preserve duplicates in its intermediate results — which is exactly what real engines do. Aggregation is the operator that forces the theoretical algebra to become a bag algebra, and it is the operator that *turns bags back into sets* along the grouping attributes: the output of gamma has, by construction, exactly one tuple per distinct grouping-key value. That second property is worth carrying into design discussions. Duplicate elimination is really the special case `gamma[all attributes; no aggregates]`, and grouping by a key is a functional-dependency-preserving operation: if `G` contains a candidate key of `R`, every group has exactly one tuple and the aggregates degenerate. ## Composition with the rest of the algebra - **Selection before aggregation** filters the input tuples; the optimizer will push such predicates below gamma whenever they reference only grouping attributes or base columns. - **Selection after aggregation** filters groups by their aggregate values — this is what a HAVING-style condition is at the algebra level, and it is simply `sigma` applied to the output of `gamma`. There is no separate operator for it. Candidates who claim HAVING needs its own algebra operator are wrong; it is selection on a derived relation. - **Projection after aggregation** drops grouping attributes you no longer need. Note that projecting away a grouping attribute after aggregating is *not* the same as never grouping by it — the counts have already been computed per finer group. ## Other extended operators in the same family Grouping/aggregation is one of a small family that real systems need beyond the basic five: outer joins (to preserve dangling tuples), duplicate-preserving bag operators, and generalised projection that computes derived expressions such as `price * qty`. Generalised projection is the mild one — it invents values, like aggregation, but from a single tuple rather than from a set. Aggregation is the one that fundamentally requires seeing a whole partition at once, which is why it is a blocking operator in physical plans and why it needs sorting or hashing to implement. ## Interview framing Say what gamma does, give the schema arithmetic, and then land the expressiveness argument: basic operators are value-preserving, aggregates are not. Add the empty-group and null-handling edges, and the point that aggregation forces bag semantics. That answers the question and the two follow-ups an interviewer usually has ready.

  • Is a HAVING-style filter a separate relational algebra operator?
    No. Once gamma has produced a relation whose attributes include the aggregate values, filtering on those values is ordinary selection applied to that relation. The only real distinction is placement: predicates on base attributes can be pushed below the aggregation, while predicates on aggregate results cannot.
  • Why does aggregation force the algebra to use bag rather than set semantics?
    Because aggregate values depend on multiplicity. If intermediate projections eliminated duplicates, SUM and COUNT over the projected values would be wrong. Engines therefore keep duplicates in intermediate results and eliminate them only where the query or the operator demands it — and gamma itself is one of the operators that produces a set along its grouping key.

saying these in an interview costs you the question

  • Claiming aggregation is derivable from selection, projection, product, union and difference
  • Expecting a group with a zero count to appear for a category that has no rows
  • Thinking AVG divides by the group size rather than by the count of non-null values
  • Treating HAVING as a distinct algebra operator instead of selection over the grouped relation
  • Assuming grouping treats each null as distinct, the way an equality predicate does

context