分页与区间
收尾方法里三兄弟管"只取一部分":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。