首页 > 代码库 > MongoDB中的聚合操作

MongoDB中的聚合操作

根据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" : 118 }

 

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" : 216                 },17                 {18                         "age" : 20,19                         "gender" : "Male",20                         "count" : 121                 },22                 {23                         "age" : 21,24                         "gender" : "Male",25                         "count" : 226                 },27                 {28                         "age" : 22,29                         "gender" : "Female",30                         "count" : 131                 }32         ],33         "count" : 6,34         "keys" : 4,35         "ok" : 136 }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.222                 },23                 {24                         "age" : 20,25                         "gender" : "Male",26                         "percentage" : 0.127                 },28                 {29                         "age" : 21,30                         "gender" : "Male",31                         "percentage" : 0.232                 },33                 {34                         "age" : 22,35                         "gender" : "Female",36                         "percentage" : 0.137                 }38         ],39         "count" : 6,40         "keys" : 4,41         "ok" : 142 }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" : 2422                 },23                 {24                         "_id" : {25                                 "gender" : "Male"26                         },27                         "value" : 2428                 }29         ],30         "timeMillis" : 2,31         "counts" : {32                 "input" : 10,33                 "emit" : 10,34                 "reduce" : 2,35                 "output" : 236         },37         "ok" : 138 }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" : 2225                 },26                 {27                         "_id" : {28                                 "gender" : "Male"29                         },30                         "value" : 21.831                 }32         ],33         "timeMillis" : 55,34         "counts" : {35                 "input" : 10,36                 "emit" : 10,37                 "reduce" : 2,38                 "output" : 239         },40         "ok" : 141 }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" : 220         },21         "ok" : 122 }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

 

MongoDB中的聚合操作