根據MongoDB的文檔描述,在MongoDB的聚合操作中,有以下五個聚合命令。
其中,count、distinct和group會提供很基本的功能,至於其他的高級聚合功能(sum、average、max、min),就需要通過mapReduce來實現了。
在MongoDB2.2版本以后,引入了新的聚合框架(聚合管道,aggregation pipeline ,使用aggregate命令),是一種基於管道概念的數據聚合操作。
Name |
Description |
count |
Counts the number of documents in a collection. |
distinct |
Displays the distinct values found for a specified key in a collection. |
group |
Groups documents in a collection by the specified key and performs simple aggregation. |
mapReduce |
Performs map-reduce aggregation for large data sets. |
aggregate |
Performs aggregation tasks such as group using the aggregation framework. |
下面就開始對這些聚合操作進行介紹,所有的測試數據都是基於上一篇文章。
count
首先,我們看下MongoDB文檔中,count命令可以支持的選項:
1 { count: <collection>, query: <query>, limit: <limit>, skip: <skip>, hint: <hint> }
- count:要執行count的collection
- query(optional):過濾條件
- limit(optional):查詢匹配文檔數量的上限
- skip(optional):跳過匹配文檔的數量
- hint(optional):使用那個索引
例子:查看男學生的數量
1 > db.runCommand({"count":"school.students", "query":{"gender":"Male"}}) 2 { "n" : 5, "ok" : 1 } 3 >
在MongoDB中,對count操作有一層包裝,所以也可以通過shell直接運行db."collectionName".count()。
但是為了保持風格一致,我還是傾向於使用db.runCommand()的方式。
distinct
接下來看看distinct命令,下面列出可以支持的選項:
1 { distinct: "<collection>", key: "<field>", query: <query> }
- distinct:要執行distinct的collection
- key:要執行distinct的鍵
- query(optional):過濾條件
例子:查看所有學生年齡的不同值
1 > db.runCommand({"distinct":"school.students","key":"age"}) 2 { 3 "values" : [ 4 20, 5 21, 6 22, 7 23, 8 24 9 ], 10 "stats" : { 11 "n" : 10, 12 "nscanned" : 10, 13 "nscannedObjects" : 0, 14 "timems" : 0, 15 "cursor" : "BtreeCursor age_1" 16 }, 17 "ok" : 1 18 }
group
group命令相比前兩就稍微復雜了一些。
1 { 2 group: 3 { 4 ns: <namespace>, 5 key: <key>, 6 $reduce: <reduce function>, 7 initial: 8 $keyf: <key function>, 9 cond: <query>, 10 finalize: <finalize function> 11 } 12 }
- ns:要執行group的collection
- key:要執行group的鍵,可以是多個鍵;和keyf兩者必須有一個
- $reduce:在group操作中要執行的聚合function,該function包括兩個參數,當前文檔和聚合結果文檔
- initial:reduce中使用變量的初始化
- $keyf(optional):可以接受一個function,用來動態的確定分組文檔的字段
- cond(optional):過濾條件
- finalize(optional):在reduce執行完成,結果集返回之前對結果集最終執行的函數
例子:統計不同年齡、性別分組的學生數量
1 > db.runCommand({ 2 ... "group":{ 3 ... "ns":"school.students", 4 ... "key":{"age":true, "gender":true}, 5 ... "initial":{"count":0}, 6 ... "$reduce": function(cur, result){ result.count++;}, 7 ... "cond":{"age":{"$lte":22}} 8 ... } 9 ... }) 10 { 11 "retval" : [ 12 { 13 "age" : 20, 14 "gender" : "Female", 15 "count" : 2 16 }, 17 { 18 "age" : 20, 19 "gender" : "Male", 20 "count" : 1 21 }, 22 { 23 "age" : 21, 24 "gender" : "Male", 25 "count" : 2 26 }, 27 { 28 "age" : 22, 29 "gender" : "Female", 30 "count" : 1 31 } 32 ], 33 "count" : 6, 34 "keys" : 4, 35 "ok" : 1 36 } 37 >
通過finalize選項,可以在結果返回之前進行一些自定義設置。
1 > db.runCommand({ 2 ... "group":{ 3 ... "ns":"school.students", 4 ... "key":{"age":true, "gender":true}, 5 ... "initial":{"count":0}, 6 ... "$reduce": function(cur, result){ 7 ... result.count++; 8 ... }, 9 ... "cond":{"age":{"$lte":22}}, 10 ... "finalize": function(result){ 11 ... result.percentage = result.count/10; 12 ... delete result.count; 13 ... } 14 ... } 15 ... }) 16 { 17 "retval" : [ 18 { 19 "age" : 20, 20 "gender" : "Female", 21 "percentage" : 0.2 22 }, 23 { 24 "age" : 20, 25 "gender" : "Male", 26 "percentage" : 0.1 27 }, 28 { 29 "age" : 21, 30 "gender" : "Male", 31 "percentage" : 0.2 32 }, 33 { 34 "age" : 22, 35 "gender" : "Female", 36 "percentage" : 0.1 37 } 38 ], 39 "count" : 6, 40 "keys" : 4, 41 "ok" : 1 42 } 43 >
mapReduce
前面三個聚合操作提供了最基本的功能,如果要用到更加復雜的聚合操作,我們就需要自己通過mapReduce來實現了。
mapReduce更重要的用法是實現多個服務器上的聚合操作。
根據MongoDB文檔,得到mapReduce的原型如下:
1 { 2 mapReduce: <collection>, 3 map: <function>, 4 reduce: <function>, 5 out: <output>, 6 query(optional): <document>, 7 sort(optional): <document>, 8 limit(optional): <number>, 9 finalize(optional): <function>, 10 scope(optional): <document>, 11 jsMode(optional): <boolean>, 12 verbose(optional): <boolean> 13 }
- mapReduce:要執行map-reduce操作的collection
- map:map function,生成鍵/值對,可以理解為映射函數
- reduce:reduce function,對map的結果進行統計,可以理解為統計函數
- out:統計結果存放集合 (不指定則使用臨時集合,在客戶端斷開后自動刪除)
- query:過濾條件
- sort:排序條件
- limit:map函數可以接受的文檔數量的最大值
- finalize:在reduce執行完成后,結果集返回之前對結果集最終執行的函數
- scope:向 map、reduce、finalize 導入外部變量
- jsMode:設置是否把map和reduce的中間數據轉換成BSON格式
- verbose:設置是否顯示詳細的時間統計信息
注意:map、reduce和finalize的函數實現都有特定的要求,具體的要求請參考MongoDB文檔
例子:
查詢男生和女生的最大年齡
1 > db.runCommand({ 2 ... "mapReduce": "school.students", 3 ... "map": function(){ 4 ... emit({gender: this.gender}, this.age); 5 ... }, 6 ... "reduce": function(key, values){ 7 ... var max = 0; 8 ... for(var i = 0; i < values.length; i++) 9 ... max = max>values[i]?max:values[i]; 10 ... return max; 11 ... }, 12 ... "out": {inline: 1}, 13 ... 14 ... }) 15 { 16 "results" : [ 17 { 18 "_id" : { 19 "gender" : "Female" 20 }, 21 "value" : 24 22 }, 23 { 24 "_id" : { 25 "gender" : "Male" 26 }, 27 "value" : 24 28 } 29 ], 30 "timeMillis" : 2, 31 "counts" : { 32 "input" : 10, 33 "emit" : 10, 34 "reduce" : 2, 35 "output" : 2 36 }, 37 "ok" : 1 38 } 39 >
分別得到男生和女生的平均年齡
1 > db.runCommand({ 2 ... "mapReduce": "school.students", 3 ... "map": function(){ 4 ... emit({gender: this.gender}, this.age); 5 ... }, 6 ... "reduce": function(key, values){ 7 ... var result = {"total": 0, "count": 0}; 8 ... for(var i = 0; i < values.length; i++) 9 ... result.total += values[i]; 10 ... result.count = values.length; 11 ... return result; 12 ... }, 13 ... "out": {inline: 1}, 14 ... "finalize": function(key, reducedValues){ 15 ... return reducedValues.total/reducedValues.count; 16 ... } 17 ... }) 18 { 19 "results" : [ 20 { 21 "_id" : { 22 "gender" : "Female" 23 }, 24 "value" : 22 25 }, 26 { 27 "_id" : { 28 "gender" : "Male" 29 }, 30 "value" : 21.8 31 } 32 ], 33 "timeMillis" : 55, 34 "counts" : { 35 "input" : 10, 36 "emit" : 10, 37 "reduce" : 2, 38 "output" : 2 39 }, 40 "ok" : 1 41 } 42 >
小技巧:關於自定義js函數
在MongoDB中,可以通過db.system.js.save命令(其中system.js是一個存放js函數的collections)來創建並保存JavaScript函數,這樣在就可以在MongoDB shell中重用這些函數。
比如,下面兩個函數是網上網友實現的
SUM |
db.system.js.save( { _id : "Sum" , value : function(key,values) { var total = 0; for(var i = 0; i < values.length; i++) total += values[i]; return total; }}); |
AVERAGE |
db.system.js.save( { _id : "Avg" , value : function(key,values) { var total = Sum(key,values); var mean = total/values.length; return mean; }}); |
通過利用上面兩個函數,我們的"分別得到男生和女生的平均年齡"例子就可以通過以下方式實現。
這個例子中,我們還特殊設置了"out"選項,把返回值存入了"average_age"這個collection中。
1 > db.runCommand({ 2 ... "mapReduce": "school.students", 3 ... "map": function(){ 4 ... emit({gender: this.gender}, this.age); 5 ... }, 6 ... "reduce": function(key, values){ 7 ... avg = Avg(key, values); 8 ... return avg; 9 ... }, 10 ... "out": {"merge": "average_age"} 11 ... }) 12 { 13 "result" : "average_age", 14 "timeMillis" : 30, 15 "counts" : { 16 "input" : 10, 17 "emit" : 10, 18 "reduce" : 2, 19 "output" : 2 20 }, 21 "ok" : 1 22 } 23 >
通過以下命令,我們可以看到新增的collection,並且查看里面的內容。
1 > show collections 2 average_age 3 school.students 4 system.indexes 5 system.js 6 > 7 > db.average_age.find() 8 { "_id" : { "gender" : "Female" }, "value" : 22 } 9 { "_id" : { "gender" : "Male" }, "value" : 21.8 } 10 > 11 > db.system.js.find() 12 { "_id" : "Sum", "value" : function (key,values) 13 { 14 var total = 0; 15 for(var i = 0; i < values.length; i++) 16 total += values[i]; 17 return total; 18 } } 19 { "_id" : "Avg", "value" : function (key,values) 20 { 21 var total = Sum(key,values); 22 var mean = total/values.length; 23 return mean; 24 } } 25 >
總結
通過這篇文章,介紹了MongoDB中count、distinct、group和mapReduce的基本使用。沒有一次把所有的聚合操作都看完,聚合管道只能放在下一次了。
Ps: 文章中使用的例子可以通過以下鏈接查看
http://files.cnblogs.com/wilber2013/aggregation.js