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 片段/函数/窗口表达式安全地嵌进查询。