聚合、分组和汇总
Prisma Client 允许您计数记录、聚合数字字段和选择不同的字段值。
聚合
Prisma Client 允许您对模型的数字字段(如 Int 和 Float)进行聚合。以下查询返回所有用户的平均年龄
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 及更高版本中,对可为空字段的聚合可以返回 number 或 null。这不包括 count,如果找不到记录,它总是返回 0。
考虑以下查询,其中模式中的 age 可为空
const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
_count: {
age: true,
},
})
{
_avg: {
age: null
},
_count: {
age: 9
}
}
在以下任何一种情况下,查询都返回 { _avg: { age: null } }
- 没有用户
- 每个用户的
age字段值都为null
这使您能够区分真实聚合值(可能为零)和无数据。
分组依据
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) - 所有非
nullname值(不是不同的值,只是非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,
},
})
选择 DISTINCT
Prisma Client 允许您使用 distinct 从 Prisma Query 响应中筛选重复行到 findMany 查询。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查询 - 内存中后处理以选择不同的
它以这种方式设计是为了支持 select 和 include 作为 distinct 查询的一部分。
以下示例在 gameId 和 playerId 上选择不同的值,按 score 排序,以返回每个玩家在每个游戏中的最高分数。该查询使用 include 和 select 来包含附加数据
- 选择
score(Play上的字段) - 选择相关的玩家姓名 (
Play和User之间的关系) - 选择相关的游戏名称 (
Play和Game之间的关系)
展开以查看示例模式
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
}
]