跳至主要内容

聚合、分组和汇总

Prisma Client 允许您统计记录、聚合数字字段和选择不同的字段值。

聚合

Prisma Client 允许您对模型的 **数字** 字段 (例如 IntFloat) 进行 aggregate 操作。以下查询返回所有用户的平均年龄

const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
})

console.log('Average age:' + aggregations._avg.age)

您可以将聚合与过滤和排序结合使用。例如,以下查询返回用户的平均年龄

  • age 升序排序
  • 其中 email 包含 prisma.io
  • 限制为 10 个用户
const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
where: {
email: {
contains: 'prisma.io',
},
},
orderBy: {
age: 'asc',
},
take: 10,
})

console.log('Average age:' + aggregations._avg.age)

聚合值可以为空

2.21.0 及更高版本中,对 **可为空字段** 的聚合操作可以返回 numbernull。这排除了 count,它在没有找到记录时始终返回 0。

考虑以下查询,其中 age 在模式中可以为空

const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
_count: {
age: true,
},
})
显示CLI结果
{
_avg: {
age: null
},
_count: {
age: 9
}
}

该查询在以下两种情况下都会返回 { _avg: { age: null } }

  • 没有用户
  • 每个用户的 age 字段的值都为 null

这使您能够区分真实的聚合值 (可能是零) 和无数据。

分组

Prisma Client 的 groupBy() 允许您按一个或多个字段值对记录进行 **分组** - 例如 countrycountrycity,并在每个组上执行 **聚合操作**,例如查找住在特定城市的人的平均年龄。groupBy()2.20.0 及更高版本中为 GA。

以下视频使用 groupBy() 汇总按大洲划分的 COVID-19 病例总数

以下示例按 country 字段对所有用户进行分组,并返回每个国家/地区的总配置文件浏览量

const groupUsers = await prisma.user.groupBy({
by: ['country'],
_sum: {
profileViews: true,
},
})
显示CLI结果

如果您在 by 选项中只有一个元素,可以使用以下简写语法来表达您的查询

const groupUsers = await prisma.user.groupBy({
by: 'country',
})

groupBy() 和过滤

groupBy() 支持两种级别的过滤:wherehaving

使用 where 过滤记录

使用 where 在 **分组之前** 过滤所有记录。以下示例按国家/地区对用户进行分组并累加配置文件浏览量,但只包括电子邮件地址包含 prisma.io 的用户

const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: {
email: {
contains: 'prisma.io',
},
},
_sum: {
profileViews: true,
},
})

使用 having 过滤组

使用 having 按聚合值 (例如字段的总和或平均值) 对 **整个组** 进行过滤,而不是对单个记录进行过滤 - 例如,只返回 平均 profileViews 大于 100 的组

const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: {
email: {
contains: 'prisma.io',
},
},
_sum: {
profileViews: true,
},
having: {
profileViews: {
_avg: {
gt: 100,
},
},
},
})
having 的用例

having 的主要用例是对聚合进行过滤。我们建议您在分组之前使用 where 将数据集的大小尽可能地减少,因为这样做 ✔ 会减少数据库需要返回的记录数量,并 ✔ 利用索引。

例如,以下查询对所有 不是 来自瑞典或加纳的用户进行分组

const fd = await prisma.user.groupBy({
by: ['country'],
where: {
country: {
notIn: ['Sweden', 'Ghana'],
},
},
_sum: {
profileViews: true,
},
having: {
profileViews: {
_min: {
gte: 10,
},
},
},
})

以下查询在技术上实现了相同的结果,但在分组之后将加纳的用户排除在外。这样做没有任何益处,并且不建议采用这种做法。

const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: {
country: {
not: 'Sweden',
},
},
_sum: {
profileViews: true,
},
having: {
country: {
not: 'Ghana',
},
profileViews: {
_min: {
gte: 10,
},
},
},
})

注意:在 having 中,您只能对聚合值 by 中可用的字段进行过滤。

groupBy() 和排序

当您将 groupBy()orderBy 结合使用时,以下约束适用

  • 您可以对 by 中存在的字段进行 orderBy
  • 您可以对聚合进行 orderBy (2.21.0 及更高版本的预览版)
  • 如果您在 groupBy() 中使用 skip 和/或 take,您还必须在查询中包含 orderBy

按聚合组排序

您可以 **按聚合组排序**。Prisma ORM 在版本 2.21.0 中添加了对关系数据库中使用 orderBy 与聚合组结合的支持,并在 3.4.0 中添加了对 MongoDB 的支持。

以下示例按每个 city 组中的用户数量对每个 city 组进行排序 (最大的组排在最前面)

const groupBy = await prisma.user.groupBy({
by: ['city'],
_count: {
city: true,
},
orderBy: {
_count: {
city: 'desc',
},
},
})
显示CLI结果

按字段排序

以下查询按国家/地区对组进行排序,跳过前两个组,并返回第三个和第四个组

const groupBy = await prisma.user.groupBy({
by: ['country'],
_sum: {
profileViews: true,
},
orderBy: {
country: 'desc',
},
skip: 2,
take: 2,
})

groupBy() 常见问题解答

我可以在 groupBy() 中使用 select 吗?

您不能在 groupBy() 中使用 select。但是,所有包含在 by 中的字段都会自动返回。

groupBy() 中使用 wherehaving 有什么区别?

where 在分组之前过滤所有记录,而 having 过滤整个组,并支持对聚合字段值 (例如该组中特定字段的平均值或总和) 进行过滤。

groupBy()distinct 有什么区别?

distinctgroupBy() 都按一个或多个唯一字段值对记录进行分组。groupBy() 允许您在每个组内聚合数据 - 例如,返回来自丹麦的帖子的平均浏览量 - 而 distinct 则不允许多做操作。

计数

统计记录

使用 count() 来统计记录或非 null 字段值的个数。以下示例查询统计所有用户

const userCount = await prisma.user.count()

统计关系

info

此功能从版本 3.0.1 开始正式提供。 要在 3.0.1 之前的版本中使用此功能,需要启用 预览功能 selectRelationCount

要返回关系数量(例如,用户的帖子数量),请使用带有嵌套 select_count 参数,如下所示

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

_count 参数

  • 可以在顶级 include *或* select 中使用
  • 可用于任何返回记录的查询(包括 deleteupdatefindFirst
  • 可以返回 多个关系计数
  • 可以 过滤关系计数(从版本 4.3.0 开始)

使用 include 返回关系计数

以下查询在结果中包含每个用户的帖子数量

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

使用 select 返回关系计数

以下查询使用 select 返回每个用户的帖子数量*以及其他字段*

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

返回多个关系计数

以下查询返回每个用户的 postsrecipes 的数量,以及其他字段

const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: {
posts: true,
recipes: true,
},
},
},
})
显示CLI结果

过滤关系计数

info

此功能从版本 4.16.0 开始正式提供。 要在版本 4.3.04.15.0 中使用此功能,需要启用 预览功能 filteredRelationCount

使用 where 过滤 _count 输出类型返回的字段。 您可以在 标量字段关系字段复合类型 的字段上执行此操作。

例如,以下查询返回所有标题为“Hello!”的用户帖子

// Count all user posts with the title "Hello!"
await prisma.user.findMany({
select: {
_count: {
select: {
posts: { where: { title: 'Hello!' } },
},
},
},
})

以下查询查找所有来自名为“Alice”的作者的评论的用户帖子

// Count all user posts that have comments
// whose author is named "Alice"
await prisma.user.findMany({
select: {
_count: {
select: {
posts: {
where: { comments: { some: { author: { is: { name: 'Alice' } } } } },
},
},
},
},
})

统计非 null 字段值

2.15.0 及更高版本中,您可以统计所有记录以及所有非 null 字段值的实例。 以下查询返回一个统计数字

  • 所有 User 记录(_all
  • 所有非 null name 值(不是不同的值,只是不为 null 的值)
const userCount = await prisma.user.count({
select: {
_all: true, // Count all records
name: true, // Count all non-null field values
},
})
显示CLI结果

过滤后的统计

count 支持过滤。 以下示例查询统计所有具有超过 100 次个人资料查看次数的用户

const userCount = await prisma.user.count({
where: {
profileViews: {
gte: 100,
},
},
})

以下示例查询统计特定用户的帖子

const postCount = await prisma.post.count({
where: {
authorId: 29,
},
})

选择不同的

Prisma Client 允许您使用 distinct 从 Prisma 查询响应中过滤重复的行到 findMany 查询。 distinct 通常与 select 结合使用,以识别表行中某些唯一的值组合。

以下示例返回具有不同 name 字段值的所有 User 记录的所有字段

const result = await prisma.user.findMany({
where: {},
distinct: ['name'],
})

以下示例返回不同的 role 字段值(例如,ADMINUSER

const distinctRoles = await prisma.user.findMany({
distinct: ['role'],
select: {
role: true,
},
})
显示CLI结果

distinct 幕后

Prisma Client 的 distinct 选项不使用 SQL SELECT DISTINCT。 相反,distinct 使用

  • SELECT 查询
  • 内存中的后处理以选择不同的

这样设计是为了支持 selectinclude 作为 distinct 查询的一部分。

以下示例在 gameIdplayerId 上选择不同的,按 score 排序,以返回每个玩家在每场比赛中的最高分数。 该查询使用 includeselect 来包含其他数据

  • 选择 scorePlay 上的字段)
  • 选择相关的玩家姓名(PlayUser 之间的关联)
  • 选择相关的游戏名称(PlayGame 之间的关联)
展开以查看示例模式
model User {
id Int @id @default(autoincrement())
name String?
play Play[]
}

model Game {
id Int @id @default(autoincrement())
name String?
play Play[]
}

model Play {
id Int @id @default(autoincrement())
score Int? @default(0)
playerId Int?
player User? @relation(fields: [playerId], references: [id])
gameId Int?
game Game? @relation(fields: [gameId], references: [id])
}
const distinctScores = await prisma.play.findMany({
distinct: ['playerId', 'gameId'],
orderBy: {
score: 'desc',
},
select: {
score: true,
game: {
select: {
name: true,
},
},
player: {
select: {
name: true,
},
},
},
})
显示CLI结果

没有 selectdistinct,查询将返回

[
{
gameId: 2,
playerId: 5
},
{
gameId: 2,
playerId: 10
}
]