# 排序

排序在查询回调里用 `q.orderBy(...)` 声明，可传**多列**、可对表达式排序。

## 单列与升降序

```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.edition, c.price])

const rows = await sqlClient.createQuery(Book, (q, book) => {
    q.orderBy(book.price.desc())         // 降序；asc() 是默认，可省
    return q.select(book.fetch(BookView))
}).fetchList()

console.log(rows)
```

## 多列与混合方向

```ts
// ② 多列：先 name 升序（默认），同名的按 edition 降序
const rows2 = await sqlClient.createQuery(Book, (q, book) => {
    q.orderBy(book.name, book.edition.desc())
    return q.select(book.fetch(BookView))
}).fetchList()

console.log(rows2)
```

顺序即优先级：前面的列为主键级排序。`orderBy` 可多次调用（追加）。

## 表达式排序与子路径

- **embedded 子路径**排序：`q.orderBy(author.name().lastName)`——embedded 属性是函数调用
  （`name()`），取到作用域后子路径 `lastName` 才是字段；
- **关联键**（引用外键）排序：`q.orderBy(book.storeId)`；
- **表达式/聚合排序**：`q.orderBy(dsl.count().desc())`（配合聚合与分组，见[「聚合与分组」](../query/aggregate)页）。

## 排序稳定性（重要）

`fetchPage`/`fetchRange` 按行切片取数——**切片稳定依赖排序唯一性**。只看一列排时，值相同的行在页与页之间顺序无保证。要分页/取 TopN，请补一个唯一键做决胜列：

```ts
q.orderBy(book.price.desc(), book.id.asc())   // 价格 + id 决胜，页间不漂移
```

## 下一步

筛选、排序都齐了。下一节是查询组的高地：**取形进阶**——扁平化、折叠、递归、输出映射与多态分支。
