2021 iThome 鐵人賽 - DAY12 Facet 與 Bucket 分桶統計

2021 iThome 鐵人賽 MongoDB披荊斬棘之路


DAY12 Facet 與 Bucket 分桶統計

之前我們介紹過了 Aggregation pipeline 了,如果不太了解,請往前看 DAY10, DAY11 的文章。

Aggregate 可以經過一堆操作呈現出我們要的結果,那如果我們要的結果是有一種以上的呈現方式怎麼辦?例如一個學校想看本校學測的學生們分析資料,一個想看按分數來分群,一個想看按班級來分群,就得準備兩次查詢語法,再分別記錄下來。

當然不用這麼麻煩,這時候就是 facet 出場的時候了,facet 能夠在一次查詢內執行多個 aggregate 並回傳結果,這樣做的好處就是來源資料只需要查詢一次。兩次可能還無法看出效果,如果是十次二十次呢?輸入的資料只需做一次,就能省掉額外的消耗。

我們先來看看 Facet 的 pattern:

1
2
3
4
5
6
7
8
db.artwork.aggregate( [
{
$facet: {
"output1": [ aggregate1-stage1 , aggregate1-stage2 ],
"output2": [ aggregate2-stage1 , aggregate2-stage2 ]
}
}
])

在使用上有些原生的限制:

  • aggregate RAM 最多使用 100 MB
  • facet out 最多只能 16 MB

知道使用規則後,我們就來準備範例的資料了。

1
2
3
4
5
6
7
8
9
10
11
12
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 },
])

在今天之前,我們想達到以下兩種統計

  • 按年份統計 有幾部電影以及總成本多少?
  • 按評分統計 有幾部電影以及總成本多少?

我們應該是會這樣寫著:

1
2
3
4
5
6
7
8
9
10
11
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 一次查詢完也是挺簡單的,語法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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'} } }
]
}
}
])

結果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
[
{
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: {} } 是刻意這樣寫的,目的只是表現不需要過濾條件時,就這樣做即可。

$bucket

aggregate 使用利器還有一個分桶的運算子,叫做 bucket (以及 bucketAuto ),功能是幫你統計的欄位進行各別統計,而分桶的方式以及刻度都能夠自行定義,便於呈現結果。這個東西算是能夠自行訂刻度的 group,來看看它的 Pattern

1
2
3
4
5
6
7
8
9
10
11
12
{
$bucket: {
groupBy: <field>,
boundaries: [ <bound_1>, ... <bound_n>],
default: <literal>,
output: {
<output1>: { <$accumulator expression> },
<output2>: { <$accumulator expression> },
...
}
}
}
  • groupBy: 分群的欄位
  • boundaries: 就是所有值的上下界,以及中間的刻度。
    已上面的範例,出版的年份從 1988~2020,我們上下界線就是 [1988, 2020],也可以自己定義範圍 [1988, 2000, 2010, 2020]
  • default: 當分群欄位的值不在上面定義的範圍時,要顯示的名稱,等下看範例就知道
  • output: 上面分群後的結果

我們使用出版年來分群,分群為 1988, 2000, 2010, 2020,剩下的就放在名為 others類別,並在每一份統計數量以及電影名字,馬上來看範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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>

這邊要特別注意的是 movie1movie7,他們的出版年是 2020,觸及了設定的 boundary 上限,也就是這個功能的上下界關係是

  • >= lower bound
  • < upper bound
  • 作者: MingYi Chou
  • 版權聲明: 轉載不用問,但請註明出處!本網誌均採用 BY-NC-SA 許可協議。