關係查詢
Prisma Client 的一個關鍵特性是能夠查詢兩個或多個模型之間的關係。關係查詢包括:
Prisma Client 還提供用於遍歷關係的流暢 API。
巢狀讀取
巢狀讀取允許你從資料庫中的多個表讀取相關資料,例如使用者及其帖子。你可以:
關係載入策略(預覽)
從版本 5.8.0 開始,你可以透過 PostgreSQL 資料庫的 relationLoadStrategy 選項,在每個查詢級別決定 Prisma Client 如何執行關係查詢(即應該應用哪種載入策略)。
從版本 5.10.0 開始,此功能也適用於 MySQL。
由於 relationLoadStrategy 選專案前處於預覽階段,你需要在 Prisma schema 檔案中透過 relationJoins 預覽功能標誌啟用它
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 結構,從而節省了應用程式級別的計算資源。
示例
你可以在任何支援 include 或 select 的查詢的頂層使用 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,
},
},
},
})
請注意,你不能在同一級別使用 select 和 include。這意味著如果你選擇 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
}
}
},
})
相反,請使用巢狀的 select 選項
const user = await prisma.user.findFirst({
select: {
// This will work!
email: true,
posts: {
select: {
title: true,
},
},
},
})
關係計數
在 3.0.1 及更高版本中,你可以include 或 select 關係計數以及欄位——例如,使用者的帖子計數。
const relationCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
過濾關係列表
當你使用 select 或 include 返回相關資料的子集時,你可以在 select 或 include 內部過濾和排序關係列表。
例如,以下查詢返回與使用者關聯的未釋出帖子標題列表
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查詢 - 使用巢狀的
createMany查詢
在大多數情況下,巢狀的 create 更受歡迎,除非需要skipDuplicates 查詢選項。這是一個描述兩種選項之間差異的快速表格
| 功能 | create | createMany | 備註 |
|---|---|---|---|
| 支援巢狀額外關係 | ✔ | ✘ * | 例如,你可以在一個查詢中建立使用者、多個帖子和每個帖子的多個評論。 * 你可以在一對一關係中手動設定外部索引鍵——例如: { 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,
},
})
請注意,無法在突出顯示的查詢中巢狀額外的 create 或 createMany,這意味著你無法同時建立使用者、帖子和帖子類別。
作為變通方法,你可以先發送一個查詢來建立將要連線的記錄,然後建立實際記錄。例如
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.io的User或 - 如果使用者不存在,則建立新的電子郵件地址為
viola@prisma.io的User
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 中巢狀 create 或 createMany 以向現有記錄新增新的相關記錄。以下查詢向 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 提供 some、every 和 none 選項,用於根據關係“多對多”側相關記錄的屬性過濾記錄。例如,根據使用者帖子的屬性過濾使用者。
例如
| 要求 | 要使用的查詢選項 |
|---|---|
"我想要一個具有至少一個未釋出 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 提供 is 和 isNot 選項,用於根據關係“-一對一”側相關記錄的屬性過濾記錄。例如,根據帖子作者的屬性過濾帖子。
例如,以下查詢返回符合以下條件的 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 開始,透過 User 到 Post
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()