# 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將所需資料挑出。
* 依照這樣的框架,就可以將多組直向接合的資料,橫向展列出。

### 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的撰寫也不會太複雜,就是設計時比較抽象,希望大家會喜歡。