跳到主要內容

關係查詢

Prisma Client 的一個關鍵特性是能夠查詢兩個或多個模型之間的關係。關係查詢包括:

Prisma Client 還提供用於遍歷關係的流暢 API

巢狀讀取

巢狀讀取允許你從資料庫中的多個表讀取相關資料,例如使用者及其帖子。你可以:

  • 使用 include 在查詢響應中包含相關記錄,例如使用者的帖子或個人資料。
  • 使用巢狀的 select 包含相關記錄的特定欄位。你也可以在 include 中巢狀 select

關係載入策略(預覽)

從版本 5.8.0 開始,你可以透過 PostgreSQL 資料庫的 relationLoadStrategy 選項,在每個查詢級別決定 Prisma Client 如何執行關係查詢(即應該應用哪種載入策略)。

從版本 5.10.0 開始,此功能也適用於 MySQL。

由於 relationLoadStrategy 選專案前處於預覽階段,你需要在 Prisma schema 檔案中透過 relationJoins 預覽功能標誌啟用它

schema.prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["relationJoins"]
}

新增此標誌後,你需要再次執行 prisma generate 以重新生成 Prisma Client。relationJoins 功能目前在 PostgreSQL、CockroachDB 和 MySQL 上可用。

Prisma Client 支援兩種關係載入策略

  • join(預設):使用資料庫級別的 LATERAL JOIN (PostgreSQL) 或相關子查詢 (MySQL),並透過一次資料庫查詢獲取所有資料。
  • query:向資料庫傳送多個查詢(每個表一個),並在應用程式級別進行連線。

這兩個選項之間的另一個重要區別是 join 策略在資料庫級別使用 JSON 聚合。這意味著它在資料庫中就已經建立了 Prisma Client 返回的 JSON 結構,從而節省了應用程式級別的計算資源。

注意:一旦 relationLoadStrategy預覽版進入正式版join 將普遍成為所有關係查詢的預設設定。

示例

你可以在任何支援 includeselect 的查詢的頂層使用 relationLoadStrategy 選項。

以下是使用 include 的示例

const users = await prisma.user.findMany({
relationLoadStrategy: 'join', // or 'query'
include: {
posts: true,
},
})

以下是另一個使用 select 的示例

const users = await prisma.user.findMany({
relationLoadStrategy: 'join', // or 'query'
select: {
posts: true,
},
})

何時使用哪種載入策略?

  • 在大多數情況下,join 策略(預設)會更有效。在 PostgreSQL 上,它結合使用 LATERAL JOINs 和 JSON 聚合來減少結果集中的冗餘,並將查詢結果轉換為預期 JSON 結構的工作委託給資料庫伺服器。在 MySQL 上,它使用相關子查詢透過單個查詢獲取結果。
  • 在某些邊緣情況下,根據資料集和查詢的特性,query 可能會表現更好。我們建議你分析資料庫查詢以識別這些情況。
  • 如果你想節省資料庫伺服器上的資源,並在應用程式伺服器中進行大量的資料合併和轉換工作(這可能更容易擴充套件),請使用 query

包含關係

以下示例返回單個使用者及其帖子

const user = await prisma.user.findFirst({
include: {
posts: true,
},
})
顯示查詢結果

包含特定關係的所有欄位

以下示例返回帖子及其作者

const post = await prisma.post.findFirst({
include: {
author: true,
},
})
顯示查詢結果

包含深度巢狀的關係

你可以巢狀 include 選項以包含關係的關係。以下示例返回使用者的帖子以及每個帖子的類別

const user = await prisma.user.findFirst({
include: {
posts: {
include: {
categories: true,
},
},
},
})
顯示查詢結果

選擇包含關係的特定欄位

你可以使用巢狀的 select 來選擇要返回的關係欄位子集。例如,以下查詢返回使用者的 name 和每個相關帖子的 title

const user = await prisma.user.findFirst({
select: {
name: true,
posts: {
select: {
title: true,
},
},
},
})
顯示查詢結果

你也可以在 include 中巢狀 select - 以下示例返回所有 User 欄位和每個帖子的 title 欄位

const user = await prisma.user.findFirst({
include: {
posts: {
select: {
title: true,
},
},
},
})
顯示查詢結果

請注意,你不能同一級別使用 selectinclude。這意味著如果你選擇 include 使用者的帖子並 select 每個帖子的標題,你不能只 select 使用者的 email

// The following query returns an exception
const user = await prisma.user.findFirst({
select: { // This won't work!
email: true
}
include: { // This won't work!
posts: {
select: {
title: true
}
}
},
})
顯示CLI結果

相反,請使用巢狀的 select 選項

const user = await prisma.user.findFirst({
select: {
// This will work!
email: true,
posts: {
select: {
title: true,
},
},
},
})

關係計數

3.0.1 及更高版本中,你可以includeselect 關係計數以及欄位——例如,使用者的帖子計數。

const relationCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
顯示查詢結果

過濾關係列表

當你使用 selectinclude 返回相關資料的子集時,你可以在 selectinclude 內部過濾和排序關係列表

例如,以下查詢返回與使用者關聯的未釋出帖子標題列表

const result = await prisma.user.findFirst({
select: {
posts: {
where: {
published: false,
},
orderBy: {
title: 'asc',
},
select: {
title: true,
},
},
},
})

你也可以使用 include 編寫相同的查詢,如下所示

const result = await prisma.user.findFirst({
include: {
posts: {
where: {
published: false,
},
orderBy: {
title: 'asc',
},
},
},
})

巢狀寫入

巢狀寫入允許你透過單次事務關係資料寫入資料庫。

巢狀寫入

  • 為在單個 Prisma Client 查詢中跨多個表建立、更新或刪除資料提供事務保證。如果查詢的任何部分失敗(例如,建立使用者成功但建立帖子失敗),Prisma Client 會回滾所有更改。
  • 支援資料模型支援的任何巢狀級別。
  • 在使用模型的建立或更新查詢時,適用於關係欄位。以下部分顯示了每個查詢可用的巢狀寫入選項。

你可以同時建立一條記錄和一條或多條相關記錄。以下查詢建立一條 User 記錄和兩條相關的 Post 記錄

const result = await prisma.user.create({
data: {
email: 'elsa@prisma.io',
name: 'Elsa Prisma',
posts: {
create: [
{ title: 'How to make an omelette' },
{ title: 'How to eat an omelette' },
],
},
},
include: {
posts: true, // Include all posts in the returned object
},
})
顯示查詢結果

有兩種方法可以建立或更新單個記錄和多個相關記錄——例如,一個使用者擁有多個帖子

在大多數情況下,巢狀的 create 更受歡迎,除非需要skipDuplicates 查詢選項。這是一個描述兩種選項之間差異的快速表格

功能createcreateMany備註
支援巢狀額外關係✘ *例如,你可以在一個查詢中建立使用者、多個帖子和每個帖子的多個評論。
* 你可以在一對一關係中手動設定外部索引鍵——例如:{ authorId: 9}
支援一對多關係例如,你可以建立一個使用者和多個帖子(一個使用者有多個帖子)
支援多對多關係例如,你可以建立一個帖子和多個類別(一個帖子可以有多個類別,一個類別可以有多個帖子)
支援跳過重複記錄使用 skipDuplicates 查詢選項。

使用巢狀的 create

以下查詢使用巢狀的 create 來建立

  • 一個使用者
  • 兩個帖子
  • 一個帖子類別

該示例還使用巢狀的 include 將所有帖子和帖子類別包含在返回的資料中。

const result = await prisma.user.create({
data: {
email: 'yvette@prisma.io',
name: 'Yvette',
posts: {
create: [
{
title: 'How to make an omelette',
categories: {
create: {
name: 'Easy cooking',
},
},
},
{ title: 'How to eat an omelette' },
],
},
},
include: {
// Include posts
posts: {
include: {
categories: true, // Include post categories
},
},
},
})
顯示查詢結果

以下是巢狀建立操作如何一次寫入資料庫中多個表的視覺表示

使用巢狀的 createMany

以下查詢使用巢狀的 createMany 來建立

  • 一個使用者
  • 兩個帖子

該示例還使用巢狀的 include 將所有帖子包含在返回的資料中。

const result = await prisma.user.create({
data: {
email: 'saanvi@prisma.io',
posts: {
createMany: {
data: [{ title: 'My first post' }, { title: 'My second post' }],
},
},
},
include: {
posts: true,
},
})
顯示查詢結果

請注意,無法在突出顯示的查詢中巢狀額外的 createcreateMany,這意味著你無法同時建立使用者、帖子和帖子類別。

作為變通方法,你可以先發送一個查詢來建立將要連線的記錄,然後建立實際記錄。例如

const categories = await prisma.category.createManyAndReturn({
data: [
{ name: 'Fun', },
{ name: 'Technology', },
{ name: 'Sports', }
],
select: {
id: true
}
});

const posts = await prisma.post.createManyAndReturn({
data: [{
title: "Funniest moments in 2024",
categoryId: categories.filter(category => category.name === 'Fun')!.id
}, {
title: "Linux or macOS — what's better?",
categoryId: categories.filter(category => category.name === 'Technology')!.id
},
{
title: "Who will win the next soccer championship?",
categoryId: categories.filter(category => category.name === 'Sports')!.id
}]
});

如果你想在單個數據庫查詢中建立所有記錄,請考慮使用$transaction型別安全的原始 SQL

你無法在 createMany()createManyAndReturn() 查詢中訪問關係,這意味著你無法在單個巢狀寫入中建立多個使用者和多個帖子。以下情況不可能發生

const createMany = await prisma.user.createMany({
data: [
{
name: 'Yewande',
email: 'yewande@prisma.io',
posts: {
// Not possible to create posts!
},
},
{
name: 'Noor',
email: 'noor@prisma.io',
posts: {
// Not possible to create posts!
},
},
],
})

連線多條記錄

以下查詢建立一個新的 User 記錄(create),並將該記錄(connect)連線到三個現有帖子

const result = await prisma.user.create({
data: {
email: 'vlad@prisma.io',
posts: {
connect: [{ id: 8 }, { id: 9 }, { id: 10 }],
},
},
include: {
posts: true, // Include all posts in the returned object
},
})
顯示查詢結果

注意:如果任何帖子記錄無法找到,Prisma Client 將丟擲異常:connect: [{ id: 8 }, { id: 9 }, { id: 10 }]

連線單個記錄

你可以將現有記錄連線到新使用者或現有使用者。以下查詢將現有帖子(id: 11)連線到現有使用者(id: 9

const result = await prisma.user.update({
where: {
id: 9,
},
data: {
posts: {
connect: {
id: 11,
},
},
},
include: {
posts: true,
},
})

連線建立記錄

如果相關記錄可能存在也可能不存在,請使用 connectOrCreate 連線相關記錄

  • 連線電子郵件地址為 viola@prisma.ioUser
  • 如果使用者不存在,則建立新的電子郵件地址為 viola@prisma.ioUser
const result = await prisma.post.create({
data: {
title: 'How to make croissants',
author: {
connectOrCreate: {
where: {
email: 'viola@prisma.io',
},
create: {
email: 'viola@prisma.io',
name: 'Viola',
},
},
},
},
include: {
author: true,
},
})
顯示查詢結果

要從記錄列表中 斷開連線 一條(例如,一篇特定的部落格文章),請提供要斷開連線的記錄的 ID 或唯一識別符號

const result = await prisma.user.update({
where: {
id: 16,
},
data: {
posts: {
disconnect: [{ id: 12 }, { id: 19 }],
},
},
include: {
posts: true,
},
})
顯示查詢結果

斷開連線 一條記錄(例如,帖子的作者),請使用 disconnect: true

const result = await prisma.post.update({
where: {
id: 23,
},
data: {
author: {
disconnect: true,
},
},
include: {
author: true,
},
})
顯示查詢結果

斷開 一對多關係中所有相關記錄(一個使用者有多個帖子),請將關係 設定 為空列表,如下所示

const result = await prisma.user.update({
where: {
id: 16,
},
data: {
posts: {
set: [],
},
},
include: {
posts: true,
},
})
顯示查詢結果

刪除所有相關的 Post 記錄

const result = await prisma.user.update({
where: {
id: 11,
},
data: {
posts: {
deleteMany: {},
},
},
include: {
posts: true,
},
})

透過刪除所有未釋出的帖子來更新使用者

const result = await prisma.user.update({
where: {
id: 11,
},
data: {
posts: {
deleteMany: {
published: false,
},
},
},
include: {
posts: true,
},
})

透過刪除特定帖子來更新使用者

const result = await prisma.user.update({
where: {
id: 6,
},
data: {
posts: {
deleteMany: [{ id: 7 }],
},
},
include: {
posts: true,
},
})

你可以使用巢狀的 updateMany 來更新特定使用者的所有相關記錄。以下查詢將特定使用者的所有帖子設定為未釋出狀態

const result = await prisma.user.update({
where: {
id: 6,
},
data: {
posts: {
updateMany: {
where: {
published: true,
},
data: {
published: false,
},
},
},
},
include: {
posts: true,
},
})
const result = await prisma.user.update({
where: {
id: 6,
},
data: {
posts: {
update: {
where: {
id: 9,
},
data: {
title: 'My updated title',
},
},
},
},
include: {
posts: true,
},
})

以下查詢使用巢狀的 upsert 來更新 "bob@prisma.io"(如果該使用者存在),或者如果使用者不存在則建立該使用者

const result = await prisma.post.update({
where: {
id: 6,
},
data: {
author: {
upsert: {
create: {
email: 'bob@prisma.io',
name: 'Bob the New User',
},
update: {
email: 'bob@prisma.io',
name: 'Bob the existing user',
},
},
},
},
include: {
author: true,
},
})

你可以在 update 中巢狀 createcreateMany 以向現有記錄新增新的相關記錄。以下查詢向 id 為 9 的使用者新增兩個帖子

const result = await prisma.user.update({
where: {
id: 9,
},
data: {
posts: {
createMany: {
data: [{ title: 'My first post' }, { title: 'My second post' }],
},
},
},
include: {
posts: true,
},
})

關係過濾器

過濾“-多對多”關係

Prisma Client 提供 someeverynone 選項,用於根據關係“多對多”側相關記錄的屬性過濾記錄。例如,根據使用者帖子的屬性過濾使用者。

例如

要求要使用的查詢選項
"我想要一個具有至少一個未釋出 Post 記錄的 User 列表"some 帖子未釋出
"我想要一個沒有未釋出 Post 記錄的 User 列表"none 帖子未釋出
"我想要一個只有未釋出 Post 記錄的 User 列表"every 帖子都未釋出

例如,以下查詢返回符合以下條件的 User

  • 沒有瀏覽量超過 100 的帖子
  • 所有帖子點贊數少於或等於 50
const users = await prisma.user.findMany({
where: {
posts: {
none: {
views: {
gt: 100,
},
},
every: {
likes: {
lte: 50,
},
},
},
},
include: {
posts: true,
},
})

過濾“-一對一”關係

Prisma Client 提供 isisNot 選項,用於根據關係“-一對一”側相關記錄的屬性過濾記錄。例如,根據帖子作者的屬性過濾帖子。

例如,以下查詢返回符合以下條件的 Post 記錄

  • 作者姓名不是 Bob
  • 作者年齡大於 40 歲
const users = await prisma.post.findMany({
where: {
author: {
isNot: {
name: 'Bob',
},
is: {
age: {
gt: 40,
},
},
},
},
include: {
author: true,
},
})

過濾不存在的“-多對多”記錄

例如,以下查詢使用 none 返回所有沒有帖子的使用者

const usersWithZeroPosts = await prisma.user.findMany({
where: {
posts: {
none: {},
},
},
include: {
posts: true,
},
})

過濾不存在的“-一對一”關係

以下查詢返回所有沒有作者關係的帖子

const postsWithNoAuthor = await prisma.post.findMany({
where: {
author: null, // or author: { }
},
include: {
author: true,
},
})

以下查詢返回所有至少有一個帖子的使用者

const usersWithSomePosts = await prisma.user.findMany({
where: {
posts: {
some: {},
},
},
include: {
posts: true,
},
})

流暢 API

流暢 API 允許你透過函式呼叫流暢地遍歷模型的關係。請注意,最後一個函式呼叫決定了整個查詢的返回型別(下面的程式碼片段中添加了相應的型別註釋以使其明確)。

此查詢返回特定 User 的所有 Post 記錄

const postsByUser: Post[] = await prisma.user
.findUnique({ where: { email: 'alice@prisma.io' } })
.posts()

這等同於以下 findMany 查詢

const postsByUser = await prisma.post.findMany({
where: {
author: {
email: 'alice@prisma.io',
},
},
})

這些查詢的主要區別在於,流暢 API 呼叫被翻譯成兩個獨立的資料庫查詢,而另一個只生成一個查詢(參見此 GitHub issue

注意:你可以利用 .findUnique({ where: { email: 'alice@prisma.io' } }).posts() 查詢由 Prisma Client 中的 Prisma dataloader 自動批處理的事實,以避免 GraphQL 解析器中的 n+1 問題

此請求返回特定帖子的所有類別

const categoriesOfPost: Category[] = await prisma.post
.findUnique({ where: { id: 1 } })
.categories()

請注意,你可以隨意串聯任意數量的查詢。在此示例中,串聯從 Profile 開始,透過 UserPost

const posts: Post[] = await prisma.profile
.findUnique({ where: { id: 1 } })
.user()
.posts()

鏈式呼叫的唯一要求是前一個函式呼叫必須只返回一個單個物件(例如,由 findUnique 查詢或“一對一關係”如 profile.user() 返回)。

以下查詢是不可能的,因為 findMany 不返回單個物件,而是返回一個列表

// This query is illegal
const posts = await prisma.user.findMany().posts()
© . This site is unofficial and not affiliated with Prisma Data, Inc.