跳至主要内容

分页

Prisma 客户端支持偏移分页和基于游标的分页。

偏移分页

偏移分页使用 `skip` 和 `take` 来跳过一定数量的结果并选择一个有限的范围。以下查询跳过前 3 个 `Post` 记录并返回记录 4 - 7

const results = await prisma.post.findMany({
skip: 3,
take: 4,
})

要实现结果页面,您只需 `skip` 页面数量乘以每页显示的结果数量。

✔ 偏移分页的优点

  • 您可以立即跳到任何页面。例如,您可以 `skip` 200 条记录并 `take` 10 条记录,这模拟了直接跳到结果集的第 21 页(底层 SQL 使用 `OFFSET`)。这在基于游标的分页中是不可能的。
  • 您可以在任何排序顺序下对相同的结果集进行分页。例如,您可以跳到按姓名字母顺序排序的 `User` 记录列表的第 21 页。这在基于游标的分页中是不可能的,它要求按唯一的、顺序的列进行排序。

✘ 偏移分页的缺点

  • 偏移分页 **在数据库级别无法扩展**。例如,如果您跳过 200,000 条记录并取前 10 条记录,数据库仍然必须遍历前 200,000 条记录才能返回您要求的 10 条记录 - 这会对性能产生负面影响。

偏移分页的用例

  • 对小型结果集进行浅分页。例如,一个博客界面,允许您按作者过滤 `Post` 记录并对结果进行分页。

示例:过滤和偏移分页

以下查询返回所有 `email` 字段包含 `prisma.io` 的记录。该查询跳过前 40 条记录并返回记录 41 - 50。

const results = await prisma.post.findMany({
skip: 40,
take: 10,
where: {
email: {
contains: 'prisma.io',
},
},
})

示例:排序和偏移分页

以下查询返回所有 `email` 字段包含 `Prisma` 的记录,并将结果按 `title` 字段排序。该查询跳过前 200 条记录并返回记录 201 - 220。

const results = await prisma.post.findMany({
skip: 200,
take: 20,
where: {
email: {
contains: 'Prisma',
},
},
orderBy: {
title: 'desc',
},
})

基于游标的分页

基于游标的分页使用 `cursor` 和 `take` 来返回给定 **游标** 前或后的有限结果集。游标为结果集中的位置添加书签,并且必须是唯一的顺序列 - 例如 ID 或时间戳。

以下示例返回包含单词 `“Prisma”` 的前 4 个 `Post` 记录并将最后一个记录的 ID 存储为 `myCursor`

**注意**:由于这是第一个查询,因此没有游标要传入。

const firstQueryResults = await prisma.post.findMany({
take: 4,
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})

// Bookmark your location in the result set - in this
// case, the ID of the last post in the list of 4.

const lastPostInResults = firstQueryResults[3] // Remember: zero-based index! :)
const myCursor = lastPostInResults.id // Example: 29

以下图表显示了前 4 个结果的 ID - 或第 1 页。下一个查询的游标为 **29**

第二个查询返回包含单词 `“Prisma”` 的前 4 个 `Post` 记录 **在提供的游标之后**(换句话说 - ID 大于 **29** 的记录)

const secondQueryResults = await prisma.post.findMany({
take: 4,
skip: 1, // Skip the cursor
cursor: {
id: myCursor,
},
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})

const lastPostInResults = secondQueryResults[3] // Remember: zero-based index! :)
const myCursor = lastPostInResults.id // Example: 52

以下图表显示了 ID 为 **29** 的记录 **之后** 的前 4 个 `Post` 记录。在本例中,新的游标为 **52**

常见问题解答

我是否总是需要 `skip: 1`?

如果您不使用 `skip: 1`,您的结果集将包含您之前的游标。第一个查询返回四个结果,游标为 **29**

不使用 `skip: 1` 时,第二个查询返回在(以及 *包含*)游标之后的 4 个结果

如果您使用 `skip: 1`,则游标不会包含在内

您可以选择使用或不使用 `skip: 1`,具体取决于您想要的分页行为。

我可以猜出游标的值吗?

如果您猜出下一个游标的值,您将跳转到结果集中的未知位置。尽管 ID 是连续的,但您无法预测增量速率(`2`、`20`、`32` 的可能性大于 `1`、`2`、`3`,尤其是在过滤后的结果集中)。

基于游标的分页是否使用底层数据库中的游标概念?

不,游标分页不使用底层数据库中的游标(例如 PostgreSQL)。

如果游标值不存在会发生什么?

使用不存在的游标将返回 `null`。Prisma 客户端不会尝试查找相邻值。

✔ 基于游标的分页的优点

  • 基于游标的分页 **可以扩展**。底层 SQL 不使用 `OFFSET`,而是查询所有 ID 大于 `cursor` 值的 `Post` 记录。

✘ 基于游标的分页的缺点

  • 您必须按游标进行排序,游标必须是唯一的顺序列。
  • 您无法仅使用游标跳转到特定页面。例如,您无法准确预测哪个游标代表第 400 页(每页 20 条记录)的开始,而无需先请求第 1 - 399 页。

基于游标的分页的用例

  • 无限滚动 - 例如,按日期/时间降序排序博客文章,并一次请求 10 篇博客文章。
  • 分批对整个结果集进行分页 - 例如,作为长时间运行的数据导出的一部分。

示例:过滤和基于游标的分页

const secondQuery = await prisma.post.findMany({
take: 4,
cursor: {
id: myCursor,
},
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})

排序和基于游标的分页

基于游标的分页要求您按顺序的唯一列进行排序,例如 ID 或时间戳。此值 - 称为游标 - 为结果集中的位置添加书签,并允许您请求下一组结果。

示例:使用基于游标的分页向后分页

要向后分页,请将 `take` 设置为负值。以下查询返回 4 个 `Post` 记录,其 `id` 小于 200,但不包括游标

const myOldCursor = 200

const firstQueryResults = await prisma.post.findMany({
take: -4,
skip: 1,
cursor: {
id: myOldCursor,
},
where: {
title: {
contains: 'Prisma' /* Optional filter */,
},
},
orderBy: {
id: 'asc',
},
})