跳到主要内容

建模和查询多对多关系

问题

在关系型数据库中建模和查询多对多关系可能具有挑战性。本文展示了如何使用 Prisma ORM 处理此问题的两个示例。第一个示例使用一个隐式多对多关系,第二个示例使用一个显式多对多关系。

解决方案

隐式关系

这是一种多对多关系类型,其中 Prisma ORM 在内部处理关系表。隐式多对多关系的基本示例如下所示

model Post {
id Int @id @default(autoincrement())
title String
tags Tag[]
}

model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}

要创建帖子及其标签,可以使用 Prisma Client 编写如下代码

await prisma.post.create({
data: {
title: 'Types of relations',
tags: { create: [{ name: 'dev' }, { name: 'prisma' }] },
},
})

在上面的示例中,我们可以直接查询帖子及其标签,如下所示

await prisma.post.findMany({
include: { tags: true },
})

获得的响应如下

[
{
"id": 1,
"title": "Types of relations",
"tags": [
{
"id": 1,
"name": "dev"
},
{
"id": 2,
"name": "prisma"
}
]
}
]

另一种用例是,如果您想添加新标签并将现有标签连接到帖子。例如,用户为其帖子创建了新标签,并选择了现有标签要添加。在这种情况下,我们可以通过以下方式实现

await prisma.post.update({
where: { id: 1 },
data: {
title: 'Prisma is awesome!',
tags: { set: [{ id: 1 }, { id: 2 }], create: { name: 'typescript' } },
},
})

显式关系

显式关系主要需要在关系表中存储额外字段的情况下创建,或者在内省已设置多对多关系的现有数据库时创建。这是上面使用的相同 Schema,但使用显式关系表

model Post {
id Int @id @default(autoincrement())
title String
tags PostTags[]
}

model PostTags {
id Int @id @default(autoincrement())
post Post? @relation(fields: [postId], references: [id])
tag Tag? @relation(fields: [tagId], references: [id])
postId Int?
tagId Int?

@@index([postId, tagId])
}

model Tag {
id Int @id @default(autoincrement())
name String @unique
posts PostTags[]
}

将标签添加到帖子中意味着在关系表(PostTags)和标签表(Tag)中进行创建

await prisma.post.create({
data: {
title: 'Types of relations',
tags: {
create: [
{ tag: { create: { name: 'dev' } } },
{ tag: { create: { name: 'prisma' } } },
],
},
},
})

查询帖子及其标签也需要额外的 include,如下所示

await prisma.post.findMany({
include: { tags: { include: { tag: true } } },
})

这将提供以下输出

[
{
"id": 1,
"title": "Types of relations",
"tags": [
{
"id": 1,
"postId": 1,
"tagId": 1,
"tag": {
"id": 1,
"name": "prisma"
}
},
{
"id": 2,
"postId": 1,
"tagId": 2,
"tag": {
"id": 2,
"name": "dev"
}
}
]
}
]

有时,在 UI 中显示关系表的数据并不理想。在这种情况下,最好在服务器端获取数据后对其进行映射,然后将该响应发送到前端。

const result = posts.map((post) => {
return { ...post, tags: post.tags.map((tag) => tag.tag) }
})

这将提供与隐式关系获得的输出类似的输出。

[
{
"id": 1,
"title": "Types of relations",
"tags": [
{
"id": 1,
"name": "prisma"
},
{
"id": 2,
"name": "dev"
}
]
}
]

本文展示了如何使用 Prisma Client 实现隐式和显式多对多关系并对其进行查询。