彙總、分組和摘要
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)
彙總值可為空值
在 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
這可讓您區分真實的彙總值(可能為零)和沒有資料的情況。
分組依據
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
時,適用以下限制
- 您可以
orderBy
by
中存在的欄位 - 您可以
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
中包含的所有欄位都會自動傳回。
將 where
和 having
與 groupBy()
一起使用有什麼區別?
where
在分組之前篩選所有記錄,而 having
篩選整個群組,並支援依據彙總欄位值進行篩選,例如該群組中特定欄位的平均值或總和。
groupBy()
和 distinct
有什麼區別?
distinct
和 groupBy()
都依據一個或多個唯一欄位值將記錄分組。groupBy()
允許您彙總每個群組內的資料 - 例如,傳回來自丹麥的貼文的平均瀏覽次數 - 而 distinct 則不允許。
計數
計數記錄
使用 count()
來計算記錄數或非 null
欄位值的數量。以下範例查詢會計算所有使用者
const userCount = await prisma.user.count()
計數關聯
若要傳回關聯的計數(例如,使用者的貼文計數),請使用 _count
參數和巢狀 select
,如下所示
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
從 Prisma Query 對 findMany
查詢的回應中篩選重複的列。distinct
通常與 select
結合使用,以識別表格列中某些唯一的值組合。
以下範例傳回具有相異 name
欄位值的所有 User
記錄的所有欄位
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
之間的關聯)
展開以查看範例 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
}
]