TSts-grm

CTE 与集合运算

当需求是"把若干查询的结果拼起来再查"(报表、分片、去重取并集),用集合运算 + 基础模型。

集合运算:unionAll

dsl.baseQuery(model, cb) 产出一个"命名查询"片段;dsl.unionAll(...) 把它们纵向拼起来:

// ① 模型(完整可运行版见工作区)
import { model, prop, dto, 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 BookView = dto.view(Book, c => [c.id, c.name, c.price])

// ② 两段查询拼一个书单:贵书 或 名含 sql 的书
//    每段 select({ book, rank }):rank 是派生列(此处 = 价格),供外层引用
const base = dsl.unionAll(
    dsl.baseQuery(Book, (q, book) => {
        q.where(book.price.gte(70))
        return q.select({
            book,
            rank: dsl.native.num `${book.price}`,
        })
    }),
    dsl.baseQuery(Book, (q, book) => {
        q.where(book.name.ilike("sql"))
        return q.select({
            book,
            rank: dsl.native.num `${book.price}`,
        })
    }),
)

unionAll 保留重复行(union 去重)。intersect/except/intersectAll/exceptAll 同族(当前版本以 unionAll 测试最充分,其余以实际支持为准)。

derivedModel:合成结果原地查询

dsl.derivedModel(...) 把上面的 base 包成一个可查询的模型(SQL 里是 from ( ... ) 别名)。外层通过 b.book 访问模型列、b.rank 访问派生列:

// ③ 对合成结果继续查询:按价格取 Top
const rows = await sqlClient.createQuery(dsl.derivedModel(base), (q, b) => {
    q.orderBy(b.book.price.desc())
    return q.select(b.book.fetch(BookView))
}).fetchList()

console.log(rows.map(r => r.name))

cteModel:先 WITH

dsl.cteModel(...) 的语义与 derivedModel 相同,只是把合成结果落成 WITH 别名 AS (...) select ...(可续引用、可读性更好):

// ④ WITH 形态:同样是“先合成、再查询”
const rows2 = await sqlClient.createQuery(dsl.cteModel(base), (q, b) => {
    q.orderBy(b.book.price.desc())
    return q.select(b.book.fetch(BookView))
}).fetchList()

console.log(rows2.map(r => r.name))

打开 SQL 追踪对比:derivedModel → from (select ... union all select ...) tb_n_;cteModel → with tb_n_(cols) as ( ... ) select ...

⚠️ 已知限制(0.0.10):cteModel 生成 WITH 语句时在 PostgreSQL 上会拼出 withtb_1_(WITH 与别名间缺空格)导致语法错误(上游该用例仅 sqlite 覆盖);沙盒示例请用 derivedModel,CTE 形态随上游修复跟进。

用途

  • 跨模型汇总:同一模型的多个过滤条件拍平再统一排序/分页;
  • 派生列共享:各段先算出 row_number()/排名等派生列,外层再按列过滤(如"取各组 top1");
  • 关联多个 baseQuery 时,join(ctx => dsl.and(...)) 也能在基础模型之间连接(复杂场景)。

下一步

SQL 的板子基本齐了。下一节是原生 SQL:dsl.native 模板——把任意 SQL 片段/函数/窗口表达式安全地嵌进查询。