之前我們介紹過了 Aggregation pipeline 了,如果不太了解,請往前看 DAY10, DAY11 的文章。
Aggregate 可以經過一堆操作呈現出我們要的結果,那如果我們要的結果是有一種以上的呈現方式怎麼辦?例如一個學校想看本校學測的學生們分析資料,一個想看按分數來分群,一個想看按班級來分群,就得準備兩次查詢語法,再分別記錄下來。
當然不用這麼麻煩,這時候就是 facet
出場的時候了,facet 能夠在一次查詢內執行多個 aggregate 並回傳結果,這樣做的好處就是來源資料只需要查詢一次。兩次可能還無法看出效果,如果是十次二十次呢?輸入的資料只需做一次,就能省掉額外的消耗。
我們先來看看 Facet 的 pattern:
db.artwork.aggregate( [
{
$facet: {
"output1": [ aggregate1-stage1 , aggregate1-stage2 ],
"output2": [ aggregate2-stage1 , aggregate2-stage2 ]
}
}
])
在使用上有些原生的限制:
知道使用規則後,我們就來準備範例的資料了。
db.facet.insertMany([
{ name: 'movie1', publishYear: 2020, rating: 9, cost: 500 },
{ name: 'movie2', publishYear: 1988, rating: 9, cost: 200 },
{ name: 'movie3', publishYear: 1988, rating: 6, cost: 700 },
{ name: 'movie4', publishYear: 2018, rating: 7, cost: 800 },
{ name: 'movie5', publishYear: 2018, rating: 4, cost: 600 },
{ name: 'movie6', publishYear: 2019, rating: 7, cost: 1200 },
{ name: 'movie7', publishYear: 2020, rating: 7, cost: 700 },
{ name: 'movie8', publishYear: 2019, rating: 7, cost: 600 },
{ name: 'movie9', publishYear: 1988, rating: 5, cost: 400 },
{ name: 'movie10', publishYear: 2018, rating: 7, cost: 800 },
])
在今天之前,我們想達到以下兩種統計
按年份統計
有幾部電影以及總成本多少?按評分統計
有幾部電影以及總成本多少?我們應該是會這樣寫著:
db.facet.aggregate(
{ '$group':
{_id:'$publishYear', totalCount:{ $sum: 1}, totalCost: {$sum:'$cost'} }
}
)
db.facet.aggregate(
{ '$group':
{_id:'$rating', totalCount:{ $sum: 1}, totalCost: {$sum:'$cost'} }
}
)
分兩次查也挺好的。謝謝大家!(被打)
使用 facet 一次查詢完也是挺簡單的,語法如下:
db.facet.aggregate([
{ $facet:
{
"groupedByPublishYear": [
{ $match: { publishYear : { $gte: 1 } } },
{ $group: {_id:'$publishYear', totalCount:{ $sum: 1}, totalCost: {$sum:'$cost'} } }
],
"groupedByRating": [
{ $match: {} },
{ $group: {_id:'$rating', totalCount:{ $sum: 1}, totalCost: {$sum:'$cost'} } }
]
}
}
])
結果如下:
{
[
{
groupedByPublishYear: [
{ _id: 1988, totalCount: 3, totalCost: 1300 },
{ _id: 2018, totalCount: 3, totalCost: 2200 },
{ _id: 2019, totalCount: 2, totalCost: 1800 },
{ _id: 2020, totalCount: 2, totalCost: 1200 }
],
groupedByRating: [
{ _id: 5, totalCount: 1, totalCost: 400 },
{ _id: 7, totalCount: 5, totalCost: 4100 },
{ _id: 4, totalCount: 1, totalCost: 600 },
{ _id: 9, totalCount: 2, totalCost: 700 },
{ _id: 6, totalCount: 1, totalCost: 700 }
]
}
]
}
其實我在使用上就是當作兩個 aggregate 在寫,先各別擊破後再組合,這樣也比較好 debug。
中間的 { $match: { publishYear : { $gte: 1 } } }
與 { $match: {} }
是刻意這樣寫的,目的只是表現不需要過濾條件時,就這樣做即可。
aggregate 使用利器還有一個分桶的運算子,叫做 bucket
(以及 bucketAuto
),功能是幫你統計的欄位進行各別統計,而分桶的方式以及刻度都能夠自行定義,便於呈現結果。這個東西算是能夠自行訂刻度的 group
,來看看它的 Pattern
{
$bucket: {
groupBy: <field>,
boundaries: [ <bound_1>, ... <bound_n>],
default: <literal>,
output: {
<output1>: { <$accumulator expression> },
<output2>: { <$accumulator expression> },
...
}
}
}
1988~2020
,我們上下界線就是 [1988, 2020]
,也可以自己定義範圍 [1988, 2000, 2010, 2020]
我們使用出版年來分群,分群為 1988, 2000, 2010, 2020,剩下的就放在名為 others
類別,並在每一份統計數量以及電影名字,馬上來看範例:
film> db.facet.aggregate([
... { $bucket:
..... {
....... groupBy: '$publishYear',
....... boundaries: [1988, 2000, 2010, 2020],
....... default: "others",
....... output: {
......... "totalCount": { $sum: 1 },
......... "names" : { $push: "$name" }
......... }
....... }
..... }
... ])
[
{
_id: 1988,
totalCount: 3,
names: [ 'movie2', 'movie3', 'movie9' ]
},
{
_id: 2010,
totalCount: 5,
names: [ 'movie4', 'movie5', 'movie6', 'movie8', 'movie10' ]
},
{
_id: 'others',
totalCount: 2,
names: [ 'movie1', 'movie7' ]
}
]
film>
這邊要特別注意的是 movie1 與 movie7,他們的出版年是 2020,觸及了設定的 boundary 上限,也就是這個功能的上下界關係是
>=
lower bound<
upper bound本系列文章會同步發表於我個人的部落格 Pie Note