跳到主要內容

彙總、分組和摘要

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 時,適用以下限制

  • 您可以 orderBy by 中存在的欄位
  • 您可以 orderBy 彙總(在 2.21.0 及更高版本中為預覽功能)
  • 如果您將 skip 和/或 takegroupBy() 一起使用,則也必須在查詢中包含 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() 常見問題

我可以將 selectgroupBy() 一起使用嗎?

您無法將 selectgroupBy() 一起使用。但是,by 中包含的所有欄位都會自動傳回。

wherehavinggroupBy() 一起使用有什麼區別?

where 在分組之前篩選所有記錄,而 having 篩選整個群組,並支援依據彙總欄位值進行篩選,例如該群組中特定欄位的平均值或總和。

groupBy()distinct 有什麼區別?

distinctgroupBy() 都依據一個或多個唯一欄位值將記錄分組。groupBy() 允許您彙總每個群組內的資料 - 例如,傳回來自丹麥的貼文的平均瀏覽次數 - 而 distinct 則不允許。

計數

計數記錄

使用 count() 來計算記錄數或非 null 欄位值的數量。以下範例查詢會計算所有使用者

const userCount = await prisma.user.count()

計數關聯

資訊

此功能在 3.0.1 及更高版本中普遍可用。若要在 3.0.1 之前的版本中使用此功能,則需要啟用 預覽功能 selectRelationCount

若要傳回關聯的計數(例如,使用者的貼文計數),請使用 _count 參數和巢狀 select,如下所示

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)
  • 所有非 null name 值(非相異值,僅為非 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 允許您使用 distinct 從 Prisma Query 對 findMany 查詢的回應中篩選重複的列。distinct 通常與 select 結合使用,以識別表格列中某些唯一的值組合。

以下範例傳回具有相異 name 欄位值的所有 User 記錄的所有欄位

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 查詢
  • 記憶體內後處理以選擇相異值

之所以如此設計,是為了支援 selectinclude 作為 distinct 查詢的一部分。

以下範例選取 gameIdplayerId 的相異值,並依 score 排序,以便傳回每位玩家在每場遊戲中的最高分數。查詢使用 includeselect 來包含其他資料

  • 選擇 score (Play 上的欄位)
  • 選擇相關的玩家名稱 (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
}
]