关系查询
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"
output = "./generated"
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将普遍成为所有关系查询的默认设置。
示例
您可以在任何支持 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,
},
},
},
})
您也可以将 select 嵌套在 include 中——以下示例返回所有 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 查询选项。这是一个快速表格,描述了这两个选项之间的区别:
| 功能 | 创建 | 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.find(category => category.name === 'Fun')!.id
}, {
title: "Linux or macOS — what's better?",
categoryId: categories.find(category => category.name === 'Technology')!.id
},
{
title: "Who will win the next soccer championship?",
categoryId: categories.find(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!
},
},
],
})
连接多个记录
以下查询创建 (create) 一个新的 User 记录并将其连接 (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 }]
连接单个记录
您可以将现有记录connect到新用户或现有用户。以下查询将现有帖子(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,
},
})
断开相关记录
要 disconnect 列表中的一个记录(例如,特定的博客帖子),请提供要断开的记录的 ID 或唯一标识符:
const result = await prisma.user.update({
where: {
id: 16,
},
data: {
posts: {
disconnect: [{ id: 12 }, { id: 19 }],
},
},
include: {
posts: true,
},
})
要 disconnect 一个记录(例如,帖子的作者),请使用 disconnect: true
const result = await prisma.post.update({
where: {
id: 23,
},
data: {
author: {
disconnect: true,
},
},
include: {
author: true,
},
})
断开所有相关记录
要disconnect一对多关系中的所有相关记录(一个用户有多个帖子),请将关系 set 为空列表,如下所示:
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 dataloader 在 Prisma Client 中自动批量处理的事实,以避免 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()