TSts-grm

聚合与分组

聚合函数以 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 的组合拳。