Skip to main content

Aggregates and grouping

An aggregate runs one query and gives you back a value of the column's type. Grouped results go into a struct you define, through Project.

One number​

adults := db.Users.Where(ant.Gt(&U.Age, 18))

total, err := ant.SumOf(ctx, adults, &U.Age) // int
average, err := ant.AvgOf(ctx, adults, &U.Age) // float64
lowest, err := ant.MinOf(ctx, adults, &U.Age) // int
last, err := ant.MaxOf(ctx, adults, &U.Name) // string

MIN and SUM over no rows are NULL in SQL. Here, they're zero.

Grouping​

Define what a result holds, give it a probe, and map columns and aggregates into it.

type AgeGroup struct {
Age int
Users int64
}

var G = ant.Fields[AgeGroup]()

groups, err := ant.Project[AgeGroup](
db.Users.GroupBy(&U.Age).Order(ant.Col(&U.Age).Asc()),
ant.Into(&G.Age, &U.Age),
ant.IntoExpr(&G.Users, ant.Count()),
).All(ctx)
SELECT "users"."age" AS "age", COUNT(*) AS "users" FROM "users" GROUP BY "users"."age" ORDER BY "users"."age"

groups is a []AgeGroup. Both sides of each mapping are probes, so the compiler checks those as well.

Filtering groups​

Where filters rows before grouping, and Having filters groups after.

groups, err := ant.Project[AgeGroup](
db.Users.
Where(ant.Gt(&U.Age, 20)).
GroupBy(&U.Age).
Having(ant.Count().Gt(1)).
Order(ant.Count().Desc()).
Limit(5),
ant.Into(&G.Age, &U.Age),
).All(ctx)

More than one column​

ant.Then adds a column to the group, and GroupByExpr groups by an expression.

byAgeAndName := ant.Then(db.Users.GroupBy(&U.Age), &U.Name)

decade := ant.SQL[int]("? / 10", ant.Col(&U.Age))

groups, err := ant.Project[AgeGroup](
db.Users.GroupByExpr(decade).Order(decade.Asc()),
ant.IntoExpr(&G.Age, decade),
ant.IntoExpr(&G.Users, ant.Count()),
).All(ctx)

Aggregate functions​

FunctionResult
ant.Count()int64, rows in the group
ant.CountOf(&U.Bio)int64, rows where it isn't NULL
ant.CountDistinct(&U.Age)int64, distinct values
ant.Sum(&U.Age)the column's type
ant.Avg(&U.Age)float64
ant.Min(&U.Age), ant.Max(&U.Age)the column's type

Each is an expression, so .Filter(...) narrows one aggregate without touching the rest of the query.

ant.IntoExpr(&G.Users, ant.Count().Filter(ant.Eq(&U.Name, "Ada")))

Across a join​

counts, err := ant.Project[AgeGroup](
db.Posts.Join(&P.User).GroupBy(&U.Age),
ant.Into(&G.Age, &U.Age),
ant.IntoExpr(&G.Users, ant.Count()),
).All(ctx)