关联查询:嵌套读取、关系过滤与 Fluent API
本教程共 54 篇 · 第 24 篇 · 更新于 2026-08-11 · 约 4 分钟阅读
本节目标:学会一次性读多层关系、在嵌套中过滤排序,掌握嵌套写入与 Fluent API。
第 21 章讲了 include 和 select 的基本用法。这一章深入关系:多级嵌套、嵌套里过滤排序、嵌套写入全家族,以及链式查询 Fluent API。
深层嵌套读取
include 可以无限往下层叠:
const user = await prisma.user.findFirst({
include: {
posts: {
include: {
categories: true,
},
},
},
});
结果里 user → posts → categories 三层结构完整带出。每一层都能继续 include 或 select,比如只取帖子的标题和分类名:
await prisma.user.findFirst({
select: {
email: true,
posts: {
select: {
title: true,
categories: { select: { name: true } },
},
},
},
});
include 与 select 同层互斥,跨层可以混用:include 里嵌 select,select 里也能嵌 include。
Tip嵌套越深,返回的数据越大。列表页通常只用到两层,第三层以上先想想是否真的需要。
嵌套关系里的过滤与排序
include 的关系可以带 where、orderBy、take:
const user = await prisma.user.findFirst({
where: { id: 1 },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: "desc" },
take: 5,
},
},
});
「取某个用户最近 5 篇已发布的帖子」一条查询完成。注意这里的 where 只作用于嵌套的 posts,不影响外层用户。
关系计数
想知道每个用户有几篇文章,不必把帖子全查出来:
await prisma.user.findMany({
include: {
_count: {
select: {
posts: true,
comments: true,
},
},
},
});
_count 还能加条件,比如只数已发布的帖子。细节见第 21 章。
嵌套写入:一次查询写多张表
嵌套写(nested writes)在一条查询里操作关联记录,整体是一个事务:任何一步失败,全部回滚。创建用户并带两篇帖子:
await prisma.user.create({
data: {
email: "elsa@prisma.io",
posts: {
create: [{ title: "第一篇" }, { title: "第二篇" }],
},
},
});
写入家族全家福:
| 操作 | 作用 |
|---|---|
| create / createMany | 新建关联记录 |
| connect | 连接已存在的记录(按唯一字段) |
| connectOrCreate | 有就连接,没有就创建 |
| disconnect | 断开连接(一对一用 disconnect: true) |
| set | 整体替换关联列表,set: [] 清空 |
| update / updateMany | 更新关联记录 |
| upsert | 存在则更新,不存在则创建 |
| deleteMany | 删除关联记录(可带条件) |
举例,给已有用户挂上三篇已有文章:
await prisma.user.update({
where: { id: 9 },
data: {
posts: { connect: [{ id: 8 }, { id: 9 }, { id: 10 }] },
},
});
connect 时记录不存在会抛错。不确定存不存在就用 connectOrCreate:
author: {
connectOrCreate: {
where: { email: "viola@prisma.io" },
create: { email: "viola@prisma.io", name: "Viola" },
},
},
批量删除一篇用户的所有草稿:
await prisma.user.update({
where: { id: 11 },
data: {
posts: { deleteMany: { published: false } },
},
});
Note嵌套写整体是一个事务,适合「用户 + 订单 + 明细」这类需要同时成功或同时失败的操作。嵌套 createMany 不能再套更深的关系,需要多层时改用嵌套 create 或 $transaction(第 26 章)。
关系过滤:回顾与加深
第 22 章的操作符在嵌套里同样可用,且能继续深入关系的关系:
await prisma.user.findMany({
where: {
posts: {
some: {
categories: { some: { name: { in: ["Food", "教程"] } } },
},
},
},
});
some / every / none 管一对多,is / isNot 管一对一,深度不限。
Fluent API:链式遍历关系
Fluent API 用方法链代替 include,一层一层往下走:
const posts = await prisma.user
.findUnique({ where: { email: "alice@prisma.io" } })
.posts();
const categories = await prisma.post
.findUnique({ where: { id: 1 } })
.categories();
链多长都行,最后一步决定返回类型。规则只有一条:上一步必须返回单个对象,所以 findUnique 之后才能继续链,findMany 之后不行。
链式写法让关系遍历代码更简洁;但要防 N+1 得靠 include 或批量 in 查询,不要在循环里逐条发链式查询。循环里逐条查关系是典型的 N+1:
// 坏:1 + N 条查询
const users = await prisma.user.findMany();
for (const user of users) {
await prisma.post.findMany({ where: { authorId: user.id } });
}
换成 include 一次带齐,或先查出主记录再按 id 批量 in 查询,查询次数就不再随记录数线性增长。
Note等价场景下,include 嵌套读取会发一条主查询 + 按主表 id 批量查关系表(共 2 条 SQL);Fluent API 一次链式调用拆成两条查询。include 一条调用把关系带齐,适合列表页一次取全;需要按需分批取关系时 Fluent API 更灵活。
参考来源
- Prisma 官方文档:Relation queries
- Mapagam:Selecting and Including Data / Working with One-to-Many Relations
- HireNodeJS:Prisma ORM in Production(N+1 Prevention)