聚合、分組和彙總
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
}
]