# 子查询

`dsl.subQuery(model, (q, table) => …)` 与根查询同样形态，但**不取回数据**——它作为表达式嵌进外层查询（IN、EXISTS 等）。

## 标量子查询：inSubQuery

```ts workspace
// ① 模型（书/作者 m2m，示例数据含 author 表，完整可运行版见工作区）
import { model, prop, dto, dsl } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'

const Author = model("Author", "id", class {
    id = prop.i64()
    name = prop.embedded({
        firstName: prop.str(50),
        lastName: prop.str(50),
    })
    books = prop.m2m(Book).mappedBy("authors")
})

const Book = model("Book", "id", class {
    id = prop.i64()
    name = prop.str(50)
    edition = prop.i32()
    price = prop.num(10, 2)
    authors = prop.m2m(Author).joinTable({
        name: "book_author_mapping",
        joinThisColumns: ["book_id"],
        joinTargetColumns: ["author_id"],
    })
})

const BookView = dto.view(Book, c => [c.id, c.name])

// ② 标量 IN：作者 Ada 写的书
const rows = await sqlClient.createQuery(Book, (q, book) => {
    q.where(book.id.inSubQuery(
        dsl.subQuery(Book, (q2, b2) => {
            // 从书-作者映射里挑出属于 Ada 的书——SQL 里是 IN (select ...)
            return q2.select(dsl.associationModel(Book, "authors").sourceId)
        })
    ))
    return q.select(book.fetch(BookView))
}).fetchList()

console.log(rows)
```

外层列 `.inSubQuery(sub)` 是最常见的形态；同族还有 `eq/ne/gt...SubQuery`（标量比较子查询）。

## 元组子查询：多列 IN

`dsl.tuple(a, b).inSubQuery(...)` 用复合键匹配：

```ts
q.where(
    dsl.tuple(order.id().x, order.id().y().a).inSubQuery(
        dsl.subQuery(ORDER_ITEM, (q, item) => {
            return q.select(item.orderIdX, item.orderIdYA)   // 列数必须与元组一致
        })
    )
)
```

## 关联模型子查询

`dsl.associationModel(模型, "关联属性")` 把**关联当作子查询的"表"**——直接站在关联上过滤：

```ts
// ③ 关联模型子查询 + notExists：没有作者的书（反例：把 = 111 的行代入）
const lonely = await sqlClient.createQuery(Book, (q, book) => {
    q.where(
        dsl.notExists(
            dsl.subQuery(dsl.associationModel(Book, "authors"), (q2, asso) => {
                q2.where(asso.sourceId.eq(book.id))          // 关联回当前书
            })
        )
    )
    return q.select(book.fetch(BookView))
}).fetchList()

console.log(lonely)   // 种子数据：3 本书都有作者 → []
```

- `asso.sourceId`（Book→authors 关联的源侧 id）；关联集合/键的更多字段可查「关联参考」；
- `dsl.exists(...)` 是 notExists 的镜像。

## 注意事项

- 子查询投影的**列数/类型**必须与用法匹配（元组 IN 尤其）；
- 子查询回调里可以 `q.where` 关联外层（`asso.sourceId.eq(book.id)`）——相关子查询，SQL 里生成 `EXISTS(select ... where source_id = outer.id)`；
- 子查询单独列出来也是「根查询」形态，几乎都能独立 run（便于调试）。

## 下一步

子查询、CTE、集合运算是"多段 SQL"的组合工具。下一节：**CTE 与集合运算**——`unionAll` 拼表与 `WITH` 公用表达式。
