聚合、分组和汇总
Prisma Client 允许您计数记录、聚合数值字段并选择不同的字段值。
聚合
Prisma Client 允许您对模型的**数值**字段(例如 Int
和 Float
)进行 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)
聚合值可为空 (nullable)
在 2.21.0 及更高版本中,对**可为空的字段**进行聚合可能返回 number
或 null
。这不包括 count
,如果未找到记录,count
始终返回 0。
考虑以下查询,其中 age
在 schema 中是可为空的:
const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
_count: {
age: true,
},
})
{
_avg: {
age: null
},
_count: {
age: 9
}
}
在以下任一情况下,查询返回 { _avg: { age: null } }
:
- 没有用户
- 每个用户的
age
字段值都为null
这允许您区分真实的聚合值(可能为零)和无数据的情况。
分组 (Group by)
Prisma Client 的 groupBy()
允许您按一个或多个字段值(例如 country
,或 country
和 city
)**对记录进行分组**,并对每个组**执行聚合**,例如查找居住在特定城市的人的平均年龄。groupBy()
在 2.20.0 及更高版本中已正式发布 (GA)。
以下视频使用 groupBy()
按大洲汇总 COVID-19 总病例数:
以下示例按 country
字段对所有用户进行分组,并返回每个国家的总资料浏览量:
const groupUsers = await prisma.user.groupBy({
by: ['country'],
_sum: {
profileViews: true,
},
})
如果 by
选项中只有一个元素,您可以使用以下简写语法来表达您的查询:
const groupUsers = await prisma.user.groupBy({
by: 'country',
})
groupBy()
和筛选
groupBy()
支持两个级别的筛选:where
和 having
。
使用 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 及更高版本的预览功能) - 如果您将
skip
和/或take
与groupBy()
一起使用,则查询中也必须包含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',
},
},
})
按字段排序
以下查询按国家对组进行排序,跳过前两个组,并返回第 3 个和第 4 个组:
const groupBy = await prisma.user.groupBy({
by: ['country'],
_sum: {
profileViews: true,
},
orderBy: {
country: 'desc',
},
skip: 2,
take: 2,
})
groupBy()
常见问题
我可以使用 select
和 groupBy()
吗?
您不能将 select
与 groupBy()
一起使用。但是,by
中包含的所有字段都会自动返回。
在 groupBy()
中使用 where
和 having
有什么区别?
where
在分组之前筛选所有记录,而 having
筛选整个组,并支持根据聚合字段值(例如该组中特定字段的平均值或总和)进行筛选。
groupBy()
和 distinct
有什么区别?
distinct
和 groupBy()
都按一个或多个唯一字段值对记录进行分组。groupBy()
允许您在每个组内聚合数据——例如,返回来自丹麦的帖子平均浏览量——而 distinct
不支持。
计数
计数记录
使用 count()
来计数记录数量或非 null
字段值。以下示例查询计数所有用户:
const userCount = await prisma.user.count()
计数关系
要返回关系的计数(例如,用户的帖子计数),请使用带有嵌套 select
的 _count
参数,如下所示:
const usersWithCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
_count
参数
- 可在顶层
include
或select
中使用 - 可与任何返回记录的查询一起使用(包括
delete
、update
和findFirst
) - 可返回 多个关系计数
- 可 筛选关系计数(从 4.3.0 版本开始)
使用 include
返回关系计数
以下查询在结果中包含每个用户的帖子计数:
const usersWithCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
使用 select
返回关系计数
以下查询使用 select
返回每个用户的帖子计数,而不返回其他字段:
const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: { posts: true },
},
},
})
返回多个关系计数
以下查询返回每个用户的 posts
和 recipes
的计数,而不返回其他字段:
const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: {
posts: true,
recipes: true,
},
},
},
})
筛选关系计数
使用 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
},
})
筛选计数
count
支持筛选。以下示例查询计数所有资料浏览量超过 100 的用户:
const userCount = await prisma.user.count({
where: {
profileViews: {
gte: 100,
},
},
})
以下示例查询计数特定用户的帖子:
const postCount = await prisma.post.count({
where: {
authorId: 29,
},
})
选择去重
Prisma Client 允许您使用 distinct
从 findMany
查询的 Prisma 查询响应中筛选重复的行。distinct
通常与 select
结合使用,以识别表中行中某些唯一的值组合。
以下示例返回所有 User
记录的所有字段,其中 name
字段值为去重后的值:
const result = await prisma.user.findMany({
where: {},
distinct: ['name'],
})
以下示例返回去重的 role
字段值(例如,ADMIN
和 USER
):
const distinctRoles = await prisma.user.findMany({
distinct: ['role'],
select: {
role: true,
},
})
distinct
的幕后原理
Prisma Client 的 distinct
选项不使用 SQL SELECT DISTINCT
。相反,distinct
使用:
- 一个
SELECT
查询 - 内存中的后处理以选择去重
这样设计是为了在 distinct
查询中**支持 select
和 include
**。
以下示例根据 gameId
和 playerId
选择去重,并按 score
排序,以返回**每个玩家在每场游戏中的最高分数**。该查询使用 include
和 select
来包含额外数据:
- 选择
score
(Play
上的字段) - 选择相关的玩家姓名(
Play
和User
之间的关系) - 选择相关的游戏名称(
Play
和Game
之间的关系)
展开查看示例 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,
},
},
},
})
如果没有 select
和 distinct
,查询将返回:
[
{
gameId: 2,
playerId: 5
},
{
gameId: 2,
playerId: 10
}
]