TSts-grm

子查询

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

标量子查询:inSubQuery

// ① 模型(书/作者 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(...) 用复合键匹配:

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(模型, "关联属性")关联当作子查询的"表"——直接站在关联上过滤:

// ③ 关联模型子查询 + 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 公用表达式。