iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Software Development

MongoDB披荊斬棘之路系列 第 10

DAY10 MongoDB 聚合(Aggregate)種類介紹

  • 分享至 

  • xImage
  •  

DAY10 MongoDB 聚合(Aggregate)種類介紹

終於來到第十天,進入比較有趣(X)、痛苦(O)的聚合了,這個算是 MongoDB 裡面比較發揮特色的開始。

MongoDB 的 Aggregation 分為兩種階段,第一種階段為 資料篩選,第二種階段為資料聚合與統計,白話來說就是一個是選擇要處理的資料,另一個是把資料整理成你想看得樣子。但要記得,這兩者間沒有順序關係,也沒有次數限制,你可以[篩選 -> 統計] 或者 [統計 -> 篩選 -> 統計] 都是可以任意安排的。

基本範例長這樣

db.employee.aggregate([
   { $match: { status: "A" } },
   { $group: { _id: "$employee_id", total: { $sum: "$field" } } }
])

MongoDB Aggregation 種類

MongoDB aggregation 分為三種,後兩種都是比較有彈性、客製化,但是方法不同。

  1. Single purpose
  2. Aggregation pipeline
  3. Map-reduce function

Single purpose

單一功能,最基本的聚合,例如你要查詢欄位不重覆的值,通常就是 distinct 語法,而在 MongoDB 內也是一樣,就是

db.employee.distinct("field_name")

這個稱為 single purpose,馬上學完 1/3 了,是不是很輕鬆。

Map-reduce function

顧名思義就是 function 做法。

Map:

var mapFunc = function(){
  emit(this.employee_id, this.employee_field);
};

Reduce:

var reduceFunc = function(empId, fields){
  return Array.sum(fields);
};

最後組合在一起

db.employee.mapReduce(
  mapFunc,
  reduceFunc,
  { out: "example_map_reduce"}
);

db.example_map_reduce.find()

{
  {"_id": 10001, "value": 33},
  {"_id": 10002, "value": 144},
  {"_id": 10003, "value": 15}
}

Aggregation pipeline

db.employee.aggregate([
  { $group: { _id: "$employee_id", value: { $sum: "$employee_field" }}},
  { $out: "example_map_reduce_2"}
])

db.example_map_reduce_2.find()


Aggregation pipeline,顧名思義所有動作就是在一個個管道中進行,每個水管大小以及形狀都是依照需求而定,比較常見的就是(但沒有一定的順序喔)

$match 找到目標資料 => ($unwind 拆解數據) => $group 資料聚合&統計 => $project 重新呈現資料樣貌 => $out 輸出

這其中還有像是 $sort$limit 沒完全列出來。

整理以一下後兩種特性:

  • map-reduce

    • 彈性高
    • 可重複使用方法
    • 不易維護
    • 效能差
  • aggregation pipeline

    • 程式碼難重複使用
    • 程式碼很多,難以閱讀
    • 效能就是比較快

查詢過去網路上的使用者經驗,map-reduce速度就是會比較慢,雖然時至今日也演進了好幾個版本,但當時已經選擇 aggregate 做法,就沒有再深入探討 map-reduce 了,如果有人在近期版本有在使用,可以分享一下使用心得。


Aggregation Operators

MongoDB aggregate 的 operator 不算太多,幾乎都很常使用到,再來會一一介紹功能以及如何使用,但在之前我們先準備好測試資料。

db.getCollection('movie').insertMany([
{"name": "movieA", "language": "en-gb", "rating": 8, "totalCost": 30000000, "producer": "companyA"},
{"name": "movieB", "language": "en-gb", "rating": 5, "totalCost": 10000000, "producer": "companyA"},
{"name": "movieC", "language": "zh-tw", "rating": 6, "totalCost": 25000000, "producer": "companyA"},
{"name": "movieD", "language": "zh-tw", "rating": 8, "totalCost": 10000000, "producer": "companyB"},
{"name": "movieE", "language": "zh-tw", "rating": 9, "totalCost": 6000000, "producer": "companyC"},
])

$sort

對特定欄位進行排序,例如我們針對rating進行倒序排序

db.movie.aggregate({"$sort" : { "rating" : -1 }})
{
    "_id" : ObjectId("6120c79d2976f517181ffefa"),
    "name" : "movieE",
    "language" : "zh-tw",
    "rating" : 9.0,
    "totalCost" : 6000000.0,
    "producer" : "companyC"
}

{
    "_id" : ObjectId("6120c79d2976f517181ffef6"),
    "name" : "movieA",
    "language" : "en-gb",
    "rating" : 8.0,
    "totalCost" : 30000000.0,
    "producer" : "companyA"
}

{
    "_id" : ObjectId("6120c79d2976f517181ffef9"),
    "name" : "movieD",
    "language" : "zh-tw",
    "rating" : 8.0,
    "totalCost" : 10000000.0,
    "producer" : "companyB"
}

{
    "_id" : ObjectId("6120c79d2976f517181ffef8"),
    "name" : "movieC",
    "language" : "zh-tw",
    "rating" : 6.0,
    "totalCost" : 25000000.0,
    "producer" : "companyA"
}

{
    "_id" : ObjectId("6120c79d2976f517181ffef7"),
    "name" : "movieB",
    "language" : "en-gb",
    "rating" : 5.0,
    "totalCost" : 10000000.0,
    "producer" : "companyA"
}

$limit

設定希望取得資料的筆數,例如我們只希望取得評價前二高的電影

db.movie.aggregate(
{"$sort" : { "rating" : -1 }},
{"$limit" : 2})

結果

{
    "_id" : ObjectId("6120c79d2976f517181ffefa"),
    "name" : "movieE",
    "language" : "zh-tw",
    "rating" : 9.0,
    "totalCost" : 6000000.0,
    "producer" : "companyC"
}

{
    "_id" : ObjectId("6120c79d2976f517181ffef9"),
    "name" : "movieD",
    "language" : "zh-tw",
    "rating" : 8.0,
    "totalCost" : 10000000.0,
    "producer" : "companyB"
}

pipeline 是依照語法的順序執行,如果今天反過來,那結果會變怎樣呢?

db.movie.aggregate(
{"$limit" : 2},
{"$sort" : { "rating" : -1 }})

結果

{
    "_id" : ObjectId("6120c79d2976f517181ffef6"),
    "name" : "movieA",
    "language" : "en-gb",
    "rating" : 8.0,
    "totalCost" : 30000000.0,
    "producer" : "companyA"
}

{
    "_id" : ObjectId("6120c79d2976f517181ffef7"),
    "name" : "movieB",
    "language" : "en-gb",
    "rating" : 5.0,
    "totalCost" : 10000000.0,
    "producer" : "companyA"
}

可以看到取出來的評價前二高電影,跟我們想像的不同,原因就出在這次 pipeline 是先取出兩名,再進行排序的。因此各位在使用上要特別注意每個 pipeline 的位置。

明天再來看稍微複雜的 operators


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


上一篇
DAY9 MongoDB 文件與嵌入式(巢狀)文件查詢(Find)
下一篇
DAY11 MongoDB 深入聚合與常見問題
系列文
MongoDB披荊斬棘之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言