# Mongodb 聚合之橫向展列 應該不是只有我遇到想到,單單想把試出的方法分享,希望對大家有所幫助。 ## 問題 假設有銷售資料如下,三個年度,三個團隊 ``` {"YEAR":2020, TEAM":"A", "SALE":1000} {"YEAR":2020, TEAM":"A", "SALE":500} {"YEAR":2020, TEAM":"B", "SALE":2000} {"YEAR":2020, TEAM":"C", "SALE":3000} {"YEAR":2021, TEAM":"A", "SALE":1000} {"YEAR":2021, TEAM":"B", "SALE":2000} {"YEAR":2021, TEAM":"B", "SALE":1000} {"YEAR":2021, TEAM":"C", "SALE":3000} {"YEAR":2022, TEAM":"A", "SALE":1000} {"YEAR":2022, TEAM":"A", "SALE":500} {"YEAR":2022, TEAM":"B", "SALE":2000} {"YEAR":2022, TEAM":"B", "SALE":1000} {"YEAR":2022, TEAM":"C", "SALE":3000} ``` 很簡單的需求,將單年度2022,與三個年度2020~2022,總共的銷售額做加總並列,表格樣式如下 | 團隊 | 2022銷售額 | 2020~2022銷售額 | | --- | -------- | -------- | | A | 1500 | 4000 | | B | 3000 | 8000 | | C | 3000 | 9000 | ## 方案 這應該很簡單!使用mongodb agregate的\$group與\$sum就搞定了!但應該要分別加總兩次。 * 想法一:做兩次aggregate,結果用pandas dataframe接合,很快也很簡單。 * 想法二:使用\$facet,在一次aggregate中做維度的計算,資料取回後,需要小小調整一樣資料形狀,再入dataframe。 以上兩個方法都蠻推薦的,也都很直覺,以mongodb的效能,都是秒內可以解決的事情。 ## 設想一個適合多維度分析的聚合框架 但這邊會發此文,也就是想變點花招,或有人早已想到這樣做,彼此勉勵精進,或有更好的方法,也請不吝讓小弟知道學習。 ### 目標 * 一次aggregate就解決,輸出的形狀容易入dataframe * 針對資料做不同區間的運算 * 不同區間維度資料,以橫向欄位展列 * 不只支援兩個,更支援N個,撰寫簡單 ### 思考脈絡 * 兩次\$group計算,使用\$unionWith做直向Row的接合。 * 注意:2022年的sale加總,與2020~2022的三年sale加總,欄位名稱要區隔,這裡三年的我姑且叫它"sale3y" * 最後再做\$group,為了將業績欄位橫向展開,\$group使用"$TEAM"欄位為_id,利用\$max將所需資料挑出。 * 依照這樣的框架,就可以將多組直向接合的資料,橫向展列出。 ![](https://i.imgur.com/4G1A7rw.png) ### aggregate演示 #### 第一次group ``` db.sales.aggregate([ {$match:{YEAR:'2022'}}, {$group:{ _id:'$TEAM', year:{$first:'YEAR'} sale:{$sum:'$SALE'} }} ]) // OUTPUT {"_id":"A", "year":2022, "sale":1500}, {"_id":"B", "year":2022, "sale":3000}, {"_id":"C", "year":2022, "sale":3000} ``` #### 第二次group ``` db.sales.aggregate([ // 第一次group區段省略... {$unionWith:{ coll:'sales', pipeline:[ {$group:{ _id:'$TEAM', sale3y:{$sum:"sale"} }} ] }} ]) // OUTPUT // 第一次group {"_id":"A", "year":2022, "sale":1500}, {"_id":"B", "year":2022, "sale":3000}, {"_id":"C", "year":2022, "sale":3000}, // 第二次group {"_id":"A", "sale3y":4000}, {"_id":"B", "sale3y":8000}, {"_id":"C", "sale3y":9000} ``` #### 最終group ``` db.sales.aggregate([ // 第一次group區段省略... // 第二次group區段省略... {$group:{ _id:'$_id', sale:{$max:'$sale'}, sale3y:{$max:'$sqle3y'} }} ]) // 最終結果 {"_id":"A", "sale":1500, "sale3y":4000}, {"_id":"B", "sale":3000, "sale3y":8000}, {"_id":"C", "sale":3000, "sale3y":9000} ``` 最後還是提醒一下,其實有時候發兩次aggregate再統合很簡單,也不會太慢。而新的\$facet其實也很不錯,就提供了多個維度的聚合,一次性撈回來,就是再資料形狀上要調整一下。 而這邊也只是用一個"偏門小招",把資料轉成我想要的樣子,讓我後續資料形狀不需要調整太多,aggregate的撰寫也不會太複雜,就是設計時比較抽象,希望大家會喜歡。