多對多關聯
多對多 (m-n) 關聯是指關係的一方可以連接到另一方的零或多個記錄的關聯。
Prisma 結構描述語法和底層資料庫中的實作,在關聯式資料庫和 MongoDB 之間有所不同。
關聯式資料庫
在關聯式資料庫中,m-n 關聯通常透過關聯表建模。m-n 關聯在 Prisma 結構描述中可以是顯式或隱式。如果您不需要在關聯表本身中儲存任何額外的元數據,我們建議使用隱式 m-n 關聯。如果需要,您可以隨時遷移到顯式 m-n 關聯。
顯式多對多關聯
在顯式 m-n 關聯中,關聯表在 Prisma 結構描述中表示為模型,並且可以在查詢中使用。顯式 m-n 關聯定義了三個模型
- 兩個具有 m-n 關聯的模型,例如
Category
和Post
。 - 一個模型,表示底層資料庫中的關聯表,例如
CategoriesOnPosts
(有時也稱為JOIN、link 或 pivot 表)。關聯表模型的欄位都是帶有相應關聯純量欄位(postId
和categoryId
)的帶註解的關聯欄位(post
和category
)。
關聯表 CategoriesOnPosts
連接相關的 Post
和 Category
記錄。在此範例中,表示關聯表的模型也定義了額外的欄位,用於描述 Post
/Category
關係 - 誰分配了類別 (assignedBy
),以及何時分配了類別 (assignedAt
)
model Post {
id Int @id @default(autoincrement())
title String
categories CategoriesOnPosts[]
}
model Category {
id Int @id @default(autoincrement())
name String
posts CategoriesOnPosts[]
}
model CategoriesOnPosts {
post Post @relation(fields: [postId], references: [id])
postId Int // relation scalar field (used in the `@relation` attribute above)
category Category @relation(fields: [categoryId], references: [id])
categoryId Int // relation scalar field (used in the `@relation` attribute above)
assignedAt DateTime @default(now())
assignedBy String
@@id([postId, categoryId])
}
底層 SQL 看起來像這樣
CREATE TABLE "Post" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Category" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
);
-- Relation table + indexes --
CREATE TABLE "CategoriesOnPosts" (
"postId" INTEGER NOT NULL,
"categoryId" INTEGER NOT NULL,
"assignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CategoriesOnPosts_pkey" PRIMARY KEY ("postId","categoryId")
);
ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "CategoriesOnPosts" ADD CONSTRAINT "CategoriesOnPosts_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
請注意,與 1-n 關聯 相同的規則適用(因為 Post
↔ CategoriesOnPosts
和 Category
↔ CategoriesOnPosts
實際上都是 1-n 關聯),這表示關聯的一方需要使用 @relation
屬性進行註解。
當您不需要將額外資訊附加到關聯時,您可以將 m-n 關聯建模為隱式 m-n 關聯。如果您未使用 Prisma Migrate,而是從內省取得資料模型,您仍然可以透過遵循 Prisma ORM 的 關聯表慣例來利用隱式 m-n 關聯。
查詢顯式多對多
以下章節示範如何查詢顯式 m-n 關聯。您可以直接查詢關聯模型 (prisma.categoriesOnPosts(...)
),或使用巢狀查詢從 Post
-> CategoriesOnPosts
-> Category
或反向查詢。
以下查詢執行三件事
- 建立
Post
- 在關聯表
CategoriesOnPosts
中建立新記錄 - 建立與新建立的
Post
記錄相關聯的新Category
const createCategory = await prisma.post.create({
data: {
title: 'How to be Bob',
categories: {
create: [
{
assignedBy: 'Bob',
assignedAt: new Date(),
category: {
create: {
name: 'New category',
},
},
},
],
},
},
})
以下查詢
- 建立新的
Post
- 在關聯表
CategoriesOnPosts
中建立新記錄 - 將類別分配連接到現有的類別(ID 為
9
和22
)
const assignCategories = await prisma.post.create({
data: {
title: 'How to be Bob',
categories: {
create: [
{
assignedBy: 'Bob',
assignedAt: new Date(),
category: {
connect: {
id: 9,
},
},
},
{
assignedBy: 'Bob',
assignedAt: new Date(),
category: {
connect: {
id: 22,
},
},
},
],
},
},
})
有時您可能不知道 Category
記錄是否存在。如果 Category
記錄存在,您想要將新的 Post
記錄連接到該類別。如果 Category
記錄不存在,您想要先建立記錄,然後將其連接到新的 Post
記錄。以下查詢
- 建立新的
Post
- 在關聯表
CategoriesOnPosts
中建立新記錄 - 將類別分配連接到現有的類別(ID 為
9
),如果不存在,則先建立新的類別
const assignCategories = await prisma.post.create({
data: {
title: 'How to be Bob',
categories: {
create: [
{
assignedBy: 'Bob',
assignedAt: new Date(),
category: {
connectOrCreate: {
where: {
id: 9,
},
create: {
name: 'New Category',
id: 9,
},
},
},
},
],
},
},
})
以下查詢傳回所有 Post
記錄,其中至少有一個 (some
) 類別分配 (categories
) 指的是名為 "New category"
的類別
const getPosts = await prisma.post.findMany({
where: {
categories: {
some: {
category: {
name: 'New Category',
},
},
},
},
})
以下查詢傳回所有類別,其中至少有一個 (some
) 相關的 Post
記錄標題包含文字 "Cool stuff"
且該類別是由 Bob 分配的。
const getAssignments = await prisma.category.findMany({
where: {
posts: {
some: {
assignedBy: 'Bob',
post: {
title: {
contains: 'Cool stuff',
},
},
},
},
},
})
以下查詢取得所有由 "Bob"
分配給 5 篇貼文之一的類別分配 (CategoriesOnPosts
) 記錄
const getAssignments = await prisma.categoriesOnPosts.findMany({
where: {
assignedBy: 'Bob',
post: {
id: {
in: [9, 4, 10, 12, 22],
},
},
},
})
隱式多對多關聯
隱式 m-n 關聯在關聯的兩側都將關聯欄位定義為清單。雖然關聯表存在於底層資料庫中,但 它由 Prisma ORM 管理,並且不會在 Prisma 結構描述中顯現。隱式關聯表遵循特定慣例。
隱式 m-n 關聯使 Prisma Client API 用於 m-n 關聯的操作更簡單一些(因為您在 巢狀寫入 內部的巢狀層級較少)。
在以下範例中,Post
和 Category
之間存在一個隱式 m-n 關聯
- 關聯式資料庫
- MongoDB
model Post {
id Int @id @default(autoincrement())
title String
categories Category[]
}
model Category {
id Int @id @default(autoincrement())
name String
posts Post[]
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
categoryIDs String[] @db.ObjectId
categories Category[] @relation(fields: [categoryIDs], references: [id])
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
postIDs String[] @db.ObjectId
posts Post[] @relation(fields: [postIDs], references: [id])
}
查詢隱式多對多
以下章節示範如何查詢隱式 m-n 關聯。與 顯式 m-n 查詢相比,查詢所需的巢狀層級較少。
以下查詢建立單一 Post
和多個 Category
記錄
const createPostAndCategory = await prisma.post.create({
data: {
title: 'How to become a butterfly',
categories: {
create: [{ name: 'Magic' }, { name: 'Butterflies' }],
},
},
})
以下查詢建立單一 Category
和多個 Post
記錄
const createCategoryAndPosts = await prisma.category.create({
data: {
name: 'Stories',
posts: {
create: [
{ title: 'That one time with the stuff' },
{ title: 'The story of planet Earth' },
],
},
},
})
以下查詢傳回所有 Post
記錄,其中包含該貼文分配的類別清單
const getPostsAndCategories = await prisma.post.findMany({
include: {
categories: true,
},
})
定義隱式 m-n 關聯的規則
隱式 m-n 關聯
-
使用關聯表的特定慣例
-
不需要
@relation
屬性,除非您需要使用名稱消除關聯歧義,例如@relation("MyRelation")
或@relation(name: "MyRelation")
。 -
如果您確實使用
@relation
屬性,則不能使用references
、fields
、onUpdate
或onDelete
引數。這是因為這些引數對於隱式 m-n 關聯採用固定值,且無法變更。 -
要求兩個模型都具有單一
@id
。請注意- 您不能使用多欄位 ID
- 您不能使用
@unique
來代替@id
資訊若要使用這些功能中的任一項,您必須改用顯式 m-n。
隱式 m-n 關聯中關聯表的慣例
如果您從內省取得資料模型,您仍然可以透過遵循 Prisma ORM 的 關聯表慣例來使用隱式 m-n 關聯。以下範例假設您想要建立關聯表,以取得兩個名為 Post
和 Category
的模型的隱式 m-n 關聯。
關聯表
如果您希望關聯表被內省作為隱式 m-n 關聯擷取,則名稱必須遵循此確切結構
- 它必須以底線
_
開頭 - 然後按字母順序排列的第一個模型的名稱(在本例中為
Category
) - 然後是關係(在本例中為
To
) - 然後按字母順序排列的第二個模型的名稱(在本例中為
Post
)
在範例中,正確的表名是 _CategoryToPost
。
當您在 Prisma 結構描述檔案中自行建立隱式 m-n 關聯時,您可以配置關聯以具有不同的名稱。這將變更資料庫中關聯表的名稱。例如,對於名為 "MyRelation"
的關聯,對應的表將稱為 _MyRelation
。
多結構描述
如果您的隱式多對多關係跨越多個資料庫結構描述(使用 multiSchema
預覽功能),則關聯表(名稱直接在上方定義,在範例中為 _CategoryToPost
)必須與按字母順序排列的第一個模型(在本例中為 Category
)位於相同的資料庫結構描述中。
欄位
隱式 m-n 關聯的關聯表必須正好有兩欄
- 指向
Category
的外鍵欄位,稱為A
- 指向
Post
的外鍵欄位,稱為B
欄位必須稱為 A
和 B
,其中 A
指向字母表中排在最前面的模型,而 B
指向字母表中排在最後面的模型。
索引
此外,還必須有
-
在兩個外鍵欄位上定義的唯一索引
CREATE UNIQUE INDEX "_CategoryToPost_AB_unique" ON "_CategoryToPost"("A" int4_ops,"B" int4_ops);
-
在 B 上定義的非唯一索引
CREATE INDEX "_CategoryToPost_B_index" ON "_CategoryToPost"("B" int4_ops);
範例
這是一個範例 SQL 陳述式,它將建立三個表格,包括索引(在 PostgreSQL 方言中),這些表格被 Prisma Introspection 擷取為隱式 m-n 關聯
CREATE TABLE "_CategoryToPost" (
"A" integer NOT NULL REFERENCES "Category"(id) ,
"B" integer NOT NULL REFERENCES "Post"(id)
);
CREATE UNIQUE INDEX "_CategoryToPost_AB_unique" ON "_CategoryToPost"("A" int4_ops,"B" int4_ops);
CREATE INDEX "_CategoryToPost_B_index" ON "_CategoryToPost"("B" int4_ops);
CREATE TABLE "Category" (
id integer SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
id integer SERIAL PRIMARY KEY
);
您可以使用不同的關係名稱在兩個表格之間定義多個多對多關係。此範例示範了 Prisma 內省在這種情況下的運作方式
CREATE TABLE IF NOT EXISTS "User" (
"id" SERIAL PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS "Video" (
"id" SERIAL PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS "_UserLikedVideos" (
"A" SERIAL NOT NULL,
"B" SERIAL NOT NULL,
CONSTRAINT "_UserLikedVideos_A_fkey" FOREIGN KEY ("A") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "_UserLikedVideos_B_fkey" FOREIGN KEY ("B") REFERENCES "Video" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS "_UserDislikedVideos" (
"A" SERIAL NOT NULL,
"B" SERIAL NOT NULL,
CONSTRAINT "_UserDislikedVideos_A_fkey" FOREIGN KEY ("A") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "_UserDislikedVideos_B_fkey" FOREIGN KEY ("B") REFERENCES "Video" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE UNIQUE INDEX "_UserLikedVideos_AB_unique" ON "_UserLikedVideos"("A", "B");
CREATE INDEX "_UserLikedVideos_B_index" ON "_UserLikedVideos"("B");
CREATE UNIQUE INDEX "_UserDislikedVideos_AB_unique" ON "_UserDislikedVideos"("A", "B");
CREATE INDEX "_UserDislikedVideos_B_index" ON "_UserDislikedVideos"("B");
如果您在此資料庫上執行 prisma db pull
,Prisma CLI 將透過內省產生以下結構描述
model User {
id Int @id @default(autoincrement())
Video_UserDislikedVideos Video[] @relation("UserDislikedVideos")
Video_UserLikedVideos Video[] @relation("UserLikedVideos")
}
model Video {
id Int @id @default(autoincrement())
User_UserDislikedVideos User[] @relation("UserDislikedVideos")
User_UserLikedVideos User[] @relation("UserLikedVideos")
}
在隱式多對多關聯中配置關聯表的名稱
使用 Prisma Migrate 時,您可以使用 @relation
屬性配置由 Prisma ORM 管理的關聯表的名稱。例如,如果您希望關聯表稱為 _MyRelationTable
而不是預設名稱 _CategoryToPost
,您可以按如下所示指定它
model Post {
id Int @id @default(autoincrement())
categories Category[] @relation("MyRelationTable")
}
model Category {
id Int @id @default(autoincrement())
posts Post[] @relation("MyRelationTable")
}
關聯表
關聯表(有時也稱為JOIN、link 或 pivot 表)連接兩個或多個其他表格,因此在它們之間建立關聯。建立關聯表是 SQL 中常見的資料建模實務,用於表示不同實體之間的關係。實質上,這表示「一個 m-n 關係在資料庫中建模為兩個 1-n 關係」。
我們建議使用隱式 m-n 關聯,其中 Prisma ORM 會自動在底層資料庫中產生關聯表。當您需要在關聯中儲存額外資料時,例如建立關聯的日期,則應使用顯式 m-n 關聯。
MongoDB
在 MongoDB 中,m-n 關聯由以下項目表示
- 兩側的關聯欄位,每個欄位都具有
@relation
屬性,以及必要的fields
和references
引數 - 每一側的參考 ID 的純量清單,其類型與另一側的 ID 欄位類型相符
以下範例示範了貼文和類別之間的 m-n 關聯
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
categoryIDs String[] @db.ObjectId
categories Category[] @relation(fields: [categoryIDs], references: [id])
}
model Category {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
postIDs String[] @db.ObjectId
posts Post[] @relation(fields: [postIDs], references: [id])
}
Prisma ORM 使用以下規則驗證 MongoDB 中的 m-n 關聯
- 關聯兩側的欄位都必須具有清單類型(在上述範例中,
categories
的類型為Category[]
,而posts
的類型為Post[]
) @relation
屬性必須在兩側都定義fields
和references
引數fields
引數必須僅定義一個純量欄位,該欄位必須為清單類型references
引數必須僅定義一個純量欄位。此純量欄位必須存在於參考模型上,並且必須與fields
引數中的純量欄位類型相同,但為單數(非清單)references
指向的純量欄位必須具有@id
屬性- 在
@relation
中不允許使用參考動作
MongoDB 不支援關聯式資料庫中使用的隱式 m-n 關聯。
查詢 MongoDB 多對多關聯
本節示範如何使用上述範例結構描述在 MongoDB 中查詢 m-n 關聯。
以下查詢尋找具有特定符合類別 ID 的貼文
const newId1 = new ObjectId()
const newId2 = new ObjectId()
const posts = await prisma.post.findMany({
where: {
categoryIDs: {
hasSome: [newId1.toHexString(), newId2.toHexString()],
},
},
})
以下查詢尋找類別名稱包含字串 'Servers'
的貼文
const posts = await prisma.post.findMany({
where: {
categories: {
some: {
name: {
contains: 'Servers',
},
},
},
},
})