Typed Aggregate ResultsSupported
Aggregations compute summary statistics over grouped rows — counts, sums, averages, min/max values. zmdb provides a typed aggregate API that returns compile-time typed results based on your aggregation specification.
Defining an Aggregate#
Use AggregateSpec<S> to declare what you want to compute:
import { type AggregateResult, type AggregateSpec } from '@zmdb/schema/dto';
const spec: AggregateSpec<Order> = {
groupBy: ['status'],
computed: {
orderCount: { fn: 'count' },
totalRevenue: { fn: 'sum', column: 'totalPrice' },
avgPrice: { fn: 'avg', column: 'totalPrice' },
minOrder: { fn: 'min', column: 'totalPrice' },
maxOrder: { fn: 'max', column: 'totalPrice' },
},
};Running the Aggregate#
Pass a builder function to aggregate() — you compose exactly what you need.
const results = await ordersRepo.aggregate(spec, agg =>
agg.groupBy('status').count('orderCount').sum('totalRevenue', 'totalPrice').avg('avgPrice', 'totalPrice').min('minOrder', 'totalPrice').max('maxOrder', 'totalPrice').compile(),
);SQL emitted:
SELECT "status",
COUNT(*) AS "orderCount",
SUM("totalPrice") AS "totalRevenue",
AVG("totalPrice") AS "avgPrice",
MIN("totalPrice") AS "minOrder",
MAX("totalPrice") AS "maxOrder"
FROM "orders"
GROUP BY "status"Typed Result#
The result type is inferred from the spec:
type OrderAgg = AggregateResult<Order, typeof spec>;
// {
// status: 'pending' | 'shipped' | 'delivered';
// orderCount: number;
// totalRevenue: number | null;
// avgPrice: number | null;
// minOrder: number | null;
// maxOrder: number | null;
// }sum and avg return number | null (NULL if no rows in group). min and max return the column's type or null. count always returns number.
Without Grouping#
Aggregate over the entire table by omitting groupBy:
const totals = await ordersRepo.aggregate(
{
computed: {
totalOrders: { fn: 'count' },
revenue: { fn: 'sum', column: 'totalPrice' },
},
},
agg => agg.count('totalOrders').sum('revenue', 'totalPrice').compile(),
);
// totals[0]: { totalOrders: number, revenue: number | null }SQL emitted:
SELECT COUNT(*) AS "totalOrders", SUM("totalPrice") AS "revenue" FROM "orders"Combining with Where#
Filter rows before aggregating by passing a pre-filtered query builder:
import { trustedTable } from '@zmdb/sql';
const recentStats = await ordersRepo.aggregate(
{
computed: { count: { fn: 'count' } },
},
agg => {
// Filter first
const q = qb.selectFrom(trustedTable('orders')).where('createdAt', '>', '2024-01-01');
// Then aggregate
return agg.count('count').compile();
},
);Push filters before aggregation for performance — the database evaluates the WHERE clause before the GROUP BY.
Cross-links#
- Read DTOs — full DTO family
- Repository — CRUD API
- Query Compiler — aggregation builder