Chapter 06

查询进阶

掌握 where 过滤器、排序与游标分页、聚合与分组、关系条件过滤,以及一次调用完成的嵌套写入。

6.1 关键概念

过滤器操作符(filter operator)
where 里的比较条件,如 equalsnotingt/gte/lt/ltecontainsstartsWithmode: "insensitive"(不区分大小写)。
逻辑组合 AND/OR/NOT
where 顶层可用 ANDORNOT 数组组合多个条件,实现复杂布尔逻辑。
游标分页(cursor pagination)
cursor+take+skip 基于上一页最后一条记录定位下一页,比 offset 分页在大数据集上更稳更快。
aggregate
聚合查询:_count_sum_avg_min_max,一次算出统计值。
groupBy
按字段分组并对每组做聚合,配合 having 过滤分组结果,等价 SQL 的 GROUP BY。
关系过滤 some/every/none
对一对多关系做条件:some(存在满足的子记录)、every(全部满足)、none(没有满足的)。一对一/多对一用 is/isNot
嵌套写入(nested writes)
在一次 create/update 中同时操作关联记录:createconnectconnectOrCreateupdateupsertdisconnectsetdelete,Prisma 自动包进事务。

6.2 where 过滤器

const rows = await prisma.user.findMany({
  where: {
    AND: [
      { age: { gte: 18, lt: 60 } },
      { email: { endsWith: "@example.com" } },
    ],
    OR: [
      { name: { contains: "ali", mode: "insensitive" } },
      { role: { in: ["ADMIN", "EDITOR"] } },
    ],
    NOT: { name: null },
  },
});

6.3 排序与分页

// offset 分页
await prisma.post.findMany({
  orderBy: [{ published: "desc" }, { createdAt: "desc" }],
  take: 20,
  skip: 40,      // 第 3 页
});

// 游标分页(大数据集推荐)
const page = await prisma.post.findMany({
  take: 20,
  skip: 1,               // 跳过游标本身
  cursor: { id: lastId },
  orderBy: { id: "asc" },
});

6.4 聚合 aggregate

const stats = await prisma.order.aggregate({
  where: { status: "PAID" },
  _count: true,
  _sum: { amount: true },
  _avg: { amount: true },
});
// { _count: 120, _sum: { amount: 98000 }, _avg: { amount: 816.6 } }

// 单独计数
const total = await prisma.user.count({ where: { role: "USER" } });

6.5 分组 groupBy

const byStatus = await prisma.order.groupBy({
  by: ["status"],
  _count: { _all: true },
  _sum: { amount: true },
  having: { amount: { _sum: { gt: 1000 } } },
  orderBy: { _sum: { amount: "desc" } },
});

6.6 关系条件过滤

// 至少发过一篇已发布文章的用户
await prisma.user.findMany({
  where: { posts: { some: { published: true } } },
});

// 所有文章都已发布 / 一篇都没发布
await prisma.user.findMany({
  where: {
    AND: [
      { posts: { every: { published: true } } },
      { posts: { none: { flagged: true } } },
    ],
  },
});

// 关系计数 _count
await prisma.user.findMany({
  include: { _count: { select: { posts: true } } },
});

6.7 嵌套写入

await prisma.user.update({
  where: { id: 1 },
  data: {
    posts: {
      create: { title: "New" },                  // 新建关联
      connect: { id: 99 },                        // 关联已有
      connectOrCreate: {                             // 有则连,无则建
        where: { slug: "prisma" },
        create: { title: "Prisma", slug: "prisma" },
      },
      updateMany: { where: { published: false }, data: { archived: true } },
      deleteMany: { where: { flagged: true } },
    },
  },
});
嵌套写入自动进事务

一个嵌套写入内的多步操作是原子的——全部成功或全部回滚,无需手动包 $transaction。多对多关系还可用 set 整体替换关联集合。

警惕 N+1

循环里逐条查关系会产生大量往返(N+1 查询)。用 include/嵌套 select 一次带出关系,或用 findMany+in 批量取,避免遍历中发查询。第 10 章细讲。

6.8 小结与下一步