# 根查询与取形

所有查询从 `sqlClient.createQuery(model, (q, table) => …)` 开始。回调里拿到**查询构建器 `q`** 与**表对象 `table`**，用链式调用描述查询，最后 `select` 定形状、收尾方法执行。

```ts workspace
// ① 模型与视图（与快速上手章一致，示例数据可直接运行）
import { model, prop, dto } 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,
])

// ② 根查询：where/orderBy 可省略，select 决定形状
const rows = await sqlClient.createQuery(Book, (q, book) => {
    return q.select(book.fetch(BookView))   // 形状 = 视图
}).fetchList()

console.log(rows)
```

## select 的三种形态

| 形态 | 写法 | 结果 |
|---|---|---|
| 视图取形 | `q.select(book.fetch(View))` | 按视图的精确 DTO，**最常用** |
| 映射投影 | `q.select({ book, rank })` | 自定义键名/混合多段表达式（`rank` 可以是原生 SQL、公式等） |
| 聚合投影 | `q.select(dsl.count())` / `q.select(book.name, dsl.max(book.edition))` | 聚合结果（配合 groupBy，见「聚合与分组」） |

映射投影示例（键名自定义，可混入列表达式）：

```ts
// ③ 映射投影：book 视图 + 价格列，键名自定
const rows2 = await sqlClient.createQuery(Book, (q, book) => {
    return q.select({
        item: book.fetch(BookView),
        price: book.price,
    })
}).fetchList()

console.log(rows2.map(r => `${r.item.name.padEnd(28)} → ${r.price}`).join('\n'))
```

## 收尾方法：按"想要几行"选择

创建查询对象后，用**一个收尾方法**执行：

| 方法 | 语义 | 返回 |
|---|---|---|
| `fetchList()` | 多行 | `T[]` |
| `fetchRequired()` | **恰好一行**：0 行 `NoDataError`、多行 `TooManyDataError` | `T` |
| `fetchOptional()` | **至多一行**：无行返回 `null`（`{ nullAsUndefined: true }` 时 `undefined`） | `T \| null` |
| `fetchCount()` | 行数 | `number` |

例：读某本书（不存在时返回 null）：

```ts
// ④ fetchOptional：至多一行
const maybe = await sqlClient.createQuery(Book, (q, book) => {
    q.where(book.id.eq(99))               // 不存在
    return q.select(book.fetch(BookView))
}).fetchOptional()

console.log('id=99 →', maybe)   // null
```

**没有 `.fetchOne()`**——"必须有且仅有一行"用 `fetchRequired()`；"至多一行"用 `fetchOptional()`。

## 查询是延迟构建的

`createQuery(...)` 本身**不执行**：只有调用收尾方法才真正生成 SQL 并执行（这也是 `createQuery` 能作为工厂被复用/传给子查询、计算属性等的原因）。

## 下一步

查询骨架有了，下一节是条件表达式的全部武器：比较、区间、IN、模糊与**动态条件**。
