Skip to content

Aggregates

Group SQL rows and compute checked aggregate projections.

Updated View as Markdown

Aggregate functions reduce rows. GROUP BY produces one result group per key; HAVING filters those groups.

totals := FROM Booking
    GROUP BY customer_id
    HAVING count() > 1
    SELECT {
        customer_id
        bookings: count()
        revenue: sum(total)
    }?

Every selected non-aggregate column must be compatible with the grouping keys.

Grouping changes the result row from source-model rows to one row per distinct key tuple. A projection should name every aggregate so its result contract is clear.

Built-ins

The portable aggregate corpus includes count, count_distinct, sum, avg, min, and max, with return types determined from the argument and engine rules. Additional aggregates may render only on selected engines.

count() counts rows. count_distinct(value) counts distinct non-null values. Other aggregates follow the selected engine’s empty-set and null rules, so use an optional or checked target where the aggregate can have no value.

stats := FROM Booking
    SELECT {
        count: count()
        average: avg(total)
        highest: max(total)
    }?

Single-row collapse

A projection containing only aggregates and no GROUP BY produces exactly one database row. In a matching checked context, the compiler can collapse the usual list carrier to a struct or record:

struct BookingStats {
    total: int
    revenue: float
}

fn booking_stats(): BookingStats ! QueryError {
    return FROM Booking
        SELECT {
            total: count()
            revenue: sum(price)
        }?
}

Field names and types must match the checked target. Without that struct-shaped context, the inferred query result remains a list of projection rows.

Collapse applies only when there is no GROUP BY and every projected value is an aggregate. Adding a plain column or a grouping key restores collection semantics.

Having

HAVING runs after grouping and may refer to grouping keys and aggregates:

rows := FROM Booking
    GROUP BY status
    HAVING sum(total) >= minimum
    SELECT { status, total: sum(total) }?

A guarded HAVING predicate if enabled uses the same fixed-statement parameter gate as guarded WHERE.

WHERE filters source rows before grouping. HAVING filters completed groups. Putting a row predicate in HAVING can change both portability and planning; use the clause matching the intended stage.

User aggregates

@sql.aggregate declarations can register compiler-known aggregate functions. Their signature and engine overloads are checked at compile time. This is not a runtime callback executed once per database row; it describes SQL supplied by the selected backend.

Keep the declared Atoll result type consistent with every engine overload. Runtime row decoding trusts the registered result shape after compilation.

Windowed aggregates are covered in Advanced.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close