跳到主要内容

建模和查询多对多关系

问题

在关系数据库中建模和查询多对多关系可能具有挑战性。本文展示了如何使用 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 查询它们。