今天介紹一個很常會用到的實作 Aggregate ,可以幫助你整合你要的資訊,同時優化 DB query 的效能,這邊將會慢慢介紹,同時 prisma 有 Aggregate 跟 Group by 兩種方法來做聚合的查詢,至於有什麼差別我們接著看下去~
在開始今天正文前,今天的內容會是基於Day21 的內容下去當作範例,model 跟 data都是基於這篇的內容 所以還沒看過的讀者記得先看一下~
Aggregate 用來讓你組合多筆資料成一個是 Int 或是 Float 的資料,例如你想查詢所有 user 的平均資料:
const aggregations = await prismaClient.user.aggregate({
_avg: {
age: true
}
})
這邊就會根據你 query 的結果去平均所有 user 的 age
{ _avg: { age: 50.12048192771084 } }
甚至進階一點你可以加上 where、orderBy、take 然後再根據你 filter 的結果去平均 age:
where 去找出 email 包含 gmail.com 的 data
user
備註 : 使用 take 記得加上 order by 排序資料喔~
const aggregations = await prismaClient.user.aggregate({
_avg: {
age: true
},
where: {
email: {
contains: 'gmail.com'
}
},
orderBy: {
age: 'asc'
},
take: 10
})
這邊就是根據你塞選的條件最終得到的結果
{ _avg: { age: 11 } }
另外除了 _avg 你也可以用 _count 查看有多少 user 讓你去做平均
const aggregations = await prismaClient.user.aggregate({
_avg: {
age: true
},
_count: {
age: true
}
})
{ _avg: { age: 50.12048192771084 }, _count: { age: 83 } }
如果當你 aggregate 的欄位他是 nullable 最後 return 的內容他可能會是 0 或是 null,如果是0代表 query 不到資料,null 就是這個欄位沒有值
const aggregations = await prismaClient.user.aggregate({
_avg: {
age: true
},
_count: {
age: true
}
})
所以請大家看看 return 的結果
{
_avg: {
age: null
},
_count: {
age: 9
}
}
上面的內容{ _avg: { age: null } } 等同於以下的其中一種情境:
user 資料user 的 age 欄位都是 null
所以這邊可以讓你區別 aggregate 的 value 他沒有資料,或是全部 age 的欄位都是 null
別於 Aggregate 只 return 單一一個 Int 或是 float 的欄位,GroupBy 在你 return 的 value 中可以讓你看到更多的欄位,以及優化你 query data 的內容,例如找到特定的 user 在特定的 city 他的平均 age 是多少
這邊簡單舉個 groupBy 的範例,以下是根據 country 去分組,然後根據分組的資料,平均整體的 profileViews
const groupUsers = await prismaClient.user.groupBy({
by: ['country'],
_sum: {
profileViews: true
}
})
從這邊 return 的 data 可以看出在 Western Sahara 這個 city 總共 profileViews 有 324 個
[
//..
{ _sum: { profileViews: 324 }, country: 'Western Sahara' }
//..
]
然後 groupBy.by 可以不止 by 一個欄位他可以接受多個
const groupUsers = await prismaClient.user.groupBy({
by: ['country', 'city'],
_sum: {
profileViews: true
}
})
這邊就是先根據 country 分組,然後再根據 city 去分類
[
//..
{
_sum: { profileViews: 324 },
country: 'Western Sahara',
city: 'Amelyhaven'
}
//..
]
如果你只需要 by 一個欄位的話,可以不用 [] 去包
const groupUsers = await prisma.user.groupBy({
by: 'country',
})
groupBy 支援 where、having 來幫你 filter data,這邊使用 where 先幫你在 group data 前先 filter data
const groupUsers = await prismaClient.user.groupBy({
by: ['country'],
where: {
email: {
contains:'gmail.com'
}
},
_sum: {
profileViews: true
}
})
[
//..
{ _sum: { profileViews: 2399 }, country: 'Montserrat' }
//..
]
having 則是 filter 已經分組好的所有欄位,例如這邊我只想找到 profileViews 的總共數量是大於 4500 的資料
const groupUsers = await prismaClient.user.groupBy({
by: ['country'],
where: {
email: {
contains: 'gmail.com'
}
},
_sum: {
profileViews: true
},
having: {
profileViews: {
_avg: {
gt: 4500,
}
}
}
})
[
{ _sum: { profileViews: 4595 }, country: 'Azerbaijan' },
{ _sum: { profileViews: 4665 }, country: 'Czechia' },
{ _sum: { profileViews: 4953 }, country: 'Gabon' },
{ _sum: { profileViews: 4943 }, country: 'Lebanon' }
]
簡單來說雖然 where 跟 having 都是用來 filter data 但是彼此差別在於 filter 時機
where 是 groupBy 前的塞選條件,用來減少 query data 的大小having 則是根據 groupBy 後的結果做指標塞選另外簡單舉個使用 Where 跟 Having 的錯誤用法,這邊主要先排除 country 不是 Sweden 跟 Ghana 的資料,然後再分組
const fd = await prisma.user.groupBy({
by: ['country'],
where: {
country: {
notIn: ['Sweden', 'Ghana'],
},
},
_sum: {
profileViews: true,
},
having: {
profileViews: {
_min: {
gte: 10,
},
},
},
})
但以下的寫法跟上面的結果是一樣的,而且這邊的 filter 的結果效能還會筆上面全部用 where 的做法來得差,所以不推薦這樣做 having
const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: {
country: {
not: 'Sweden',
},
},
_sum: {
profileViews: true,
},
having: {
country: {
not: 'Ghana',
},
profileViews: {
_min: {
gte: 10,
},
},
},
})
Order By 也可以用在 groupBy :
order 根據 by 的欄位order aggregate 的結果例如 _sum
take 或是 skip 必須加上 order by
這邊就是根據以上的 demo 排序資料結果
const groupUsers = await prismaClient.user.groupBy({
by: ['country'],
where: {
email: {
contains: 'gmail.com'
}
},
_sum: {
profileViews: true
},
orderBy: {
_sum: {
profileViews: 'asc'
}
},
having: {
profileViews: {
_avg: {
gt: 4500
}
}
}
})
如此我們就可以排序 profileViews 的內容了
[
{ _sum: { profileViews: 4595 }, country: 'Azerbaijan' },
{ _sum: { profileViews: 4665 }, country: 'Czechia' },
{ _sum: { profileViews: 4943 }, country: 'Lebanon' },
{ _sum: { profileViews: 4953 }, country: 'Gabon' }
]
這邊使用 skip 排除第一筆資料,同時只拿根據 order by 的兩筆內容
const groupUsers = await prismaClient.user.groupBy({
by: ['country'],
where: {
email: {
contains: 'gmail.com'
}
},
_sum: {
profileViews: true
},
orderBy: {
_sum: {
profileViews: 'asc'
}
},
having: {
profileViews: {
_avg: {
gt: 4500
}
}
},
skip: 2,
take: 1
})
[ { _sum: { profileViews: 4943 }, country: 'Lebanon' } ]
A1 你不能在 groupBy() 中使用 select。因為,by 中包含的所有欄位都會自動返回。
A2 where 在分組之前過濾所有記錄,而 having 過濾整個群組,並支援對聚合欄位值進行過濾,例如該組中特定欄位的平均值或總和。
aggregate 和 groupBy() 都以一個或多個唯一欄位值將記錄分組。 groupBy() 允許你在每個群組內聚合資料,例如返回 post 上的平均瀏覽次數 , 而 aggregate 則不行,只能單純 return 例如 _sum 等等的 Int 或是 Float 的資料。
✅ 前端社群 :
https://lihi3.cc/kBe0Y