終於來到第十天,進入比較有趣(X)、痛苦(O)的聚合了,這個算是 MongoDB 裡面比較發揮特色的開始。
MongoDB 的 Aggregation 分為兩種階段,第一種階段為 資料篩選
,第二種階段為資料聚合與統計
,白話來說就是一個是選擇要處理的資料,另一個是把資料整理成你想看得樣子。但要記得,這兩者間沒有順序關係,也沒有次數限制,你可以[篩選 -> 統計]
或者 [統計 -> 篩選 -> 統計]
都是可以任意安排的。
基本範例長這樣
db.employee.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$employee_id", total: { $sum: "$field" } } }
])
MongoDB aggregation 分為三種,後兩種都是比較有彈性、客製化,但是方法不同。
單一功能,最基本的聚合,例如你要查詢欄位不重覆的值,通常就是 distinct
語法,而在 MongoDB 內也是一樣,就是
db.employee.distinct("field_name")
這個稱為 single purpose,馬上學完 1/3 了,是不是很輕鬆。
顧名思義就是 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}
}
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 了,如果有人在近期版本有在使用,可以分享一下使用心得。
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"},
])
對特定欄位進行排序,例如我們針對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"
}
設定希望取得資料的筆數,例如我們只希望取得評價前二高的電影
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