聚合与分组
聚合函数以 dsl.count() / dsl.sum(...) 等形式作为 select 投影出现,结果直接是标量(或按组展开)。
全局聚合:select 一个标量
// ① 模型(完整可运行版见工作区)
import { model, prop, dsl } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'
const Book = model("Book", "id", class {
id = prop.i64()
name = prop.str(50)
edition = prop.i32()
price = prop.num(10, 2)
})
// ② 全局聚合:书的本数 + 最高价
const summary = await sqlClient.createQuery(Book, (q, book) => {
return q.select({
count: dsl.count(),
maxPrice: dsl.max(book.price),
})
}).fetchList()
console.log(summary) // [{ count: 3, maxPrice: 79 }]
| 函数 | 语义 |
|---|---|
dsl.count() | 行数 |
dsl.sum(expr) / dsl.avg(expr) | 求和 / 平均 |
dsl.min(expr) / dsl.max(expr) | 最小 / 最大 |
聚合也可直接 q.select(dsl.count()) 得到单个标量行的数组(fetchRequired 取第一行)。
分组:groupBy
需要按组聚合时用 q.groupBy(...),分组列与聚合列一起 select:
// ③ 按版次分组:每个 edition 的本数
const rows = await sqlClient.createQuery(Book, (q, book) => {
q.groupBy(book.edition)
return q.select({
edition: book.edition,
count: dsl.count(),
})
}).fetchList()
console.log(rows) // 按 edition 分组计数
分组列是普通列投影;同时取非分组列(如 max(book.name) 于组内取字典序最大)也合法。
组合使用
// ④ 分组 + 条件 + 组内聚合
const rows2 = await sqlClient.createQuery(Book, (q, book) => {
q.where(book.price.gte(40)) // 先过滤
q.groupBy(book.edition) // 再分组
return q.select({
edition: book.edition,
count: dsl.count(),
best: dsl.max(book.name),
})
}).fetchList()
console.log(rows2)
where 先于分组生效(筛的是行);对分组后的过滤(HAVING 语义)当前版本用子查询实现(见「子查询」页)。
⚠️ 已知限制(0.0.10):
groupBy之后直接orderBy会生成非法 SQL(syntax error at or near "group")——需要按组排序时,先把分组结果做成子查询/CTE(见「CTE 与集合运算」)再排;随上游修复跟进。
下一步
聚合能力覆盖日常报表。下一节子查询:IN/EXISTS 与关联子查询——多条 SQL 的组合拳。