# CTE 与集合运算

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

## 集合运算：unionAll

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

```ts workspace
// ① 模型（完整可运行版见工作区）
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` 访问派生列：

```ts
// ③ 对合成结果继续查询：按价格取 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 ...`（可续引用、可读性更好）：

```ts
// ④ 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 ...`。

## 用途

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

## 下一步

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