相关文章推荐
刚毅的打火机  ·  browsermob-proxy与selen ...·  2 月前    · 
独立的柠檬  ·  基于GeoTools做GeoJson,Sha ...·  10 月前    · 
阳光的酱肘子  ·  Kubernetes系统精讲 ...·  1 年前    · 

DAY12 MongoDB Facet 與 Bucket 分桶統計

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

DAY10 MongoDB 聚合(Aggregate)種類介紹 DAY11 MongoDB 深入聚合與常見問題

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

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

我們先來看看 Facet 的 pattern:

db.artwork.aggregate( [
    $facet: {
      "output1": [ aggregate1-stage1 , aggregate1-stage2 ],
      "output2": [ aggregate2-stage1 , aggregate2-stage2 ]

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

  • aggregate RAM 最多使用 100 MB
  • facet out 最多只能 16 MB
  • 知道使用規則後,我們就來準備範例的資料了。

    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: {} } 是刻意這樣寫的,目的只是表現不需要過濾條件時,就這樣做即可。

    $bucket

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

    $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類別,並在每一份統計數量以及電影名字,馬上來看範例:

    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' ] 
    

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

    >= lower bound < upper bound

    本系列文章會同步發表於我個人的部落格 Pie Note