跳到主要内容

聚合、分组和汇总

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,如果在没有找到记录的情况下,count 总是返回 0。

考虑以下查询,其中 age 在 schema 中是可空的

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() 允许您按一个或多个字段值(例如 country,或 countrycity对记录进行分组,并对每个组执行聚合,例如查找居住在特定城市的人的平均年龄。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 组进行排序

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

按字段排序

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

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()

计数关联

信息

此功能在 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结果

过滤关联计数

信息

此功能在 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)
  • 所有非 nullname 值(不是不同的值,只是非 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 允许您使用 distinctfindMany 查询的 Prisma 查询响应中过滤重复行。distinct 通常与 select 结合使用,以标识表中行中某些值的独特组合。

以下示例返回所有 User 记录的所有字段,其中 name 字段值是不同的

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 查询
  • 内存中的后处理以选择不同的值

这样设计是为了在 distinct 查询中支持 selectinclude

以下示例对 gameIdplayerId 进行 distinct 选择,并按 score 排序,以返回每个玩家在每场游戏中的最高得分。该查询使用 includeselect 包含附加数据

  • 选择 scorePlay 上的字段)
  • 选择相关的玩家名称(PlayUser 之间的关联)
  • 选择相关的游戏名称(PlayGame 之间的关联)
展开查看示例 schema
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
}
]