跳至主要内容

关系查询

Prisma Client 的一个主要特性是能够查询两个或多个模型之间的关系。关系查询包括:

Prisma Client 还提供了一个流畅的 API 用于遍历关系

嵌套读取

嵌套读取允许您从数据库中的多个表中读取相关数据,例如用户及其帖子。您可以:

  • 使用 include 在查询响应中包含相关记录,例如用户的帖子或个人资料。
  • 使用嵌套的 select 来包含相关记录中的特定字段。您也可以将 select 嵌套在 include 中。

关系加载策略(预览)

自版本 5.8.0 起,您可以通过 PostgreSQL 数据库的 relationLoadStrategy 选项,在每个查询级别决定 Prisma Client 如何执行关系查询(即应应用何种加载策略)。

自版本 5.10.0 起,此功能也适用于 MySQL。

由于 relationLoadStrategy 选项目前处于预览阶段,您需要在 Prisma schema 文件中通过 relationJoins 预览功能标志启用它。

schema.prisma
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 将普遍成为所有关系查询的默认设置。

示例

您可以在任何支持 includeselect 的查询的顶层使用 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,
},
})
显示query结果

包含特定关系的所有字段

以下示例返回帖子及其作者:

const post = await prisma.post.findFirst({
include: {
author: true,
},
})
显示query结果

包含深度嵌套关系

您可以嵌套 include 选项以包含关系的子关系。以下示例返回用户的帖子以及每个帖子的类别:

const user = await prisma.user.findFirst({
include: {
posts: {
include: {
categories: true,
},
},
},
})
显示query结果

选择包含关系中的特定字段

您可以使用嵌套的 select 来选择要返回的关系字段子集。例如,以下查询返回用户的 name 和每个相关帖子的 title

const user = await prisma.user.findFirst({
select: {
name: true,
posts: {
select: {
title: true,
},
},
},
})
显示query结果

您也可以将 select 嵌套在 include 中——以下示例返回所有 User 字段和每个帖子的 title 字段:

const user = await prisma.user.findFirst({
include: {
posts: {
select: {
title: true,
},
},
},
})
显示query结果

请注意,您不能同一级别使用 selectinclude。这意味着如果您选择 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
}
}
},
})
显示CLI结果

而是使用嵌套的 select 选项:

const user = await prisma.user.findFirst({
select: {
// This will work!
email: true,
posts: {
select: {
title: true,
},
},
},
})

关系计数

3.0.1 及更高版本中,您可以includeselect 关系的计数以及字段——例如,用户的帖子计数。

const relationCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
显示query结果

筛选关系列表

当您使用 selectinclude 返回相关数据的子集时,您可以在 selectinclude 内部筛选和排序关系列表

例如,以下查询返回与用户关联的未发布帖子的标题列表:

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
},
})
显示query结果

有两种方法可以创建或更新单个记录和多个相关记录——例如,一个用户拥有多个帖子:

在大多数情况下,嵌套的 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
},
},
},
})
显示query结果

以下是嵌套创建操作如何一次写入数据库中多个表的视觉表示:

使用嵌套的 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,
},
})
显示query结果

请注意,无法在突出显示的查询中嵌套额外的 createcreateMany,这意味着您无法同时创建用户、帖子和帖子类别。

作为一种变通方法,您可以先发送查询来创建要连接的记录,然后再创建实际的记录。例如:

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
},
})
显示query结果

注意:如果任何帖子记录无法找到,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.ioUser
  • 如果用户不存在,则创建邮箱地址为 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,
},
})
显示query结果

disconnect 列表中的一个记录(例如,特定的博客帖子),请提供要断开的记录的 ID 或唯一标识符:

const result = await prisma.user.update({
where: {
id: 16,
},
data: {
posts: {
disconnect: [{ id: 12 }, { id: 19 }],
},
},
include: {
posts: true,
},
})
显示query结果

disconnect 一个记录(例如,帖子的作者),请使用 disconnect: true

const result = await prisma.post.update({
where: {
id: 23,
},
data: {
author: {
disconnect: true,
},
},
include: {
author: true,
},
})
显示query结果

disconnect一对多关系中的所有相关记录(一个用户有多个帖子),请将关系 set 为空列表,如下所示:

const result = await prisma.user.update({
where: {
id: 16,
},
data: {
posts: {
set: [],
},
},
include: {
posts: true,
},
})
显示query结果

删除所有相关的 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 中嵌套 createcreateMany,以向现有记录添加新的相关记录。以下查询向 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 提供 someeverynone 选项,用于根据关系 "-多对多" 侧相关记录的属性筛选记录。例如,根据用户帖子的属性筛选用户。

例如

要求要使用的查询选项
“我想要一个包含至少一个未发布 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 提供了 isisNot 选项,用于根据关系 "-一对一" 侧相关记录的属性筛选记录。例如,根据帖子作者的属性筛选帖子。

例如,以下查询返回满足以下条件的 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 开始,经过 UserPost

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()
© . This site is unofficial and not affiliated with Prisma Data, Inc.