跳到主要內容

分頁

Prisma Client 支援偏移分頁和基於游標的分頁。

偏移分頁

偏移分頁使用 skiptake 來跳過一定數量的結果並選取有限的範圍。以下查詢跳過前 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',
},
})

基於游標的分頁

基於游標的分頁使用 cursortake,以傳回指定游標之前或之後的有限結果集。游標會標記您在結果集中的位置,且必須是唯一的循序欄位,例如 ID 或時間戳記。

以下範例傳回前 4 筆包含單字 "Prisma"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 個結果(或第 1 頁)的 ID。下一個查詢的游標為 29

第二個查詢傳回在提供的游標之後(換句話說 - ID 大於 29)包含單字 "Prisma" 的前 4 筆 Post 記錄

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 是循序的,但您無法預測遞增率(22032123 更可能,尤其是在篩選後的結果集中)。

基於游標的分頁是否使用底層資料庫中的游標概念?

否,游標分頁不使用底層資料庫中的游標(例如 PostgreSQL)。

如果游標值不存在會發生什麼事?

使用不存在的游標會傳回 null。Prisma Client 不會嘗試尋找相鄰的值。

✔ 基於游標的分頁的優點

  • 基於游標的分頁可擴展。底層 SQL 不使用 OFFSET,而是查詢 ID 大於 cursor 值的全部 Post 記錄。

✘ 基於游標的分頁的缺點

  • 您必須依游標排序,游標必須是唯一的循序欄位。
  • 您無法僅使用游標跳到特定頁面。例如,如果不先請求第 1 - 399 頁,您就無法準確預測哪個游標代表第 400 頁的開始(頁面大小為 20)。

基於游標的分頁的使用案例

  • 無限捲動 - 例如,依日期/時間降序排序部落格文章,並一次請求 10 篇部落格文章。
  • 以批次方式分頁整個結果集 - 例如,作為長時間執行的資料匯出的一部分。

範例:篩選和基於游標的分頁

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

排序和基於游標的分頁

基於游標的分頁要求您依循序的唯一欄位(例如 ID 或時間戳記)排序。此值(稱為游標)會標記您在結果集中的位置,並讓您請求下一組。

範例:使用基於游標的分頁向後分頁

若要向後分頁,請將 take 設定為負值。以下查詢傳回 4 筆 id 小於 200 的 Post 記錄,不包含游標

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',
},
})