TSts-grm

分页与区间

收尾方法里三兄弟管"只取一部分":fetchPage(总量感知的分页)、fetchRange(纯切片)、fetchCount(只数行)。

fetchPage:分页 + 总数

// ① 模型与视图(完整可运行版见工作区)
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])

// ② 分页:pageNo 从 1 起(缺省 1),pageSize 必填
const page = await sqlClient.createQuery(Book, (q, book) => {
    q.orderBy(book.price.desc(), book.id.asc())   // 稳定排序,见「排序」
    return q.select(book.fetch(BookView))
}).fetchPage({ pageNo: 1, pageSize: 2 })

console.log({
    totalRowCount: page.totalRowCount,   // 3
    totalPageCount: page.totalPageCount, // 2
    pageNo: page.pageNo,                 // 1
    isFirstPage: page.isFirstPage,       // true
    isLastPage: page.isLastPage,         // false
    rows: page.rows,                     // 第 1 页 2 行
})

Page<T> 六件套:totalRowCount / totalPageCount / pageNo / isFirstPage / isLastPage / rows。翻到不存在的页(如第 9 页)返回空 rows、总数不变——分页状态自己按 pageNo 维护即可。

fetchRange:不要总数的切片

只要"第 offset+1 行起取 limit 行"(列表滚动加载、流式取数),fetchRange 不做 count,省一次查询:

// ③ 区间:跳过第 1 行取 2 行(等价 limit 2 offset 1)
const rows = await sqlClient.createQuery(Book, (q, book) => {
    q.orderBy(book.price.desc(), book.id.asc())
    return q.select(book.fetch(BookView))
}).fetchRange({ limit: 2, offset: 1 })

console.log(rows)   // 第 2、3 名

fetchCount:只数行

// ④ 计数:满足条件的行数
const n = await sqlClient.createQuery(Book, (q, book) => {
    q.where(book.price.gt(50))
    return q.select(book.fetch(BookView))
}).fetchCount()

console.log(n)   // 2

计数同样走完整条件链(where/关联过滤都生效),只是不取行。

纪律:切片必须配稳定排序

fetchPage/fetchRange 都是"按排序序切片"。排序不唯一时同名行跨页漂移。规则:切片查询的 orderBy 至少包含一个唯一列做决胜:

q.orderBy(book.price.desc(), book.id.asc())   // 价格相同时按 id 定序

下一步

单表查询的骨架完整了:根查询 → 条件 → 排序 → 取形 → 分页。下一节进入聚合与分组:count/sum/avg 与 groupBy/having。