MongoDB中的聚合操作 - 田小计划

标签: mongodb 聚合 计划 | 发表时间:2014-12-03 22:24 | 作者:田小计划
出处:

根据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

 


本文链接: MongoDB中的聚合操作,转载请注明。

相关 [mongodb 聚合 计划] 推荐:

MongoDB中的聚合操作 - 田小计划

- - 博客园_首页
根据MongoDB的文档描述,在MongoDB的聚合操作中,有以下五个聚合命令. 其中,count、distinct和group会提供很基本的功能,至于其他的高级聚合功能(sum、average、max、min),就需要通过mapReduce来实现了. 在MongoDB2.2版本以后,引入了新的聚合框架(聚合管道,aggregation pipeline ,使用aggregate命令),是一种基于管道概念的数据聚合操作.

[mongodb] java操作mongodb

- - 数据库 - ITeye博客
           //实例化Mongo对象,连接27017端口.                               //连接名为yourdb的数据库,假如数据库不存在的话,mongodb会自动建立. //从Mongodb中获得名为yourColleection的数据集合,如果该数据集合不存在,Mongodb会为其新建立.

【MongoDB】MongoDB之优化器Profiler

- - CSDN博客数据库推荐文章
在mysql数据库中,慢查询日志经常作为优化数据库的依据, mongodb中依然有类似的功能. Mongodb自带的profiler,可以方便地记录所有耗时的操作,以便于调优;. 一、开始profiler功能. 开启profier功能有两种:. 第一种就是直接在启动参数里面进行设置,就在茄冬mongodb时候添加-profile=级别.

夜说mongodb

- Lianhui Wang - NoSQLFan
前两天本站刚刚分享了wordnik使用MongoDB经验的文章:《Wordnik 的 MongoDB 使用经历》,今天又看到一位朋友对这方面做的总结,分享在这里,供大家参考. 赋闲以后很长没有更新博客了,说忙完全是借口,多半因为没有兴致所致. 今天凌晨比赛多多,趁着比赛的前奏和间隙,遂浏览些技术文章.

MongoDB与内存

- 高春辉 - 火丁笔记
但凡初次接触MongoDB的人,无不惊讶于它对内存的贪得无厌,至于个中缘由,我先讲讲Linux是如何管理内存的,再说说MongoDB是如何使用内存的,答案自然就清楚了. 据说带着问题学习更有效,那就先看一个MongoDB服务器的top命令结果:. 这台MongoDB服务器有没有性能问题. 先讲讲Linux是如何管理内存的.

白话MongoDB(一)

- Ease - 江边潮未尽,枫红一季秋
按照官方的说法,MongoDB是一种可扩展的高性能的开源的面向文档(document-oriented )的数据库,采用C++开发. 注意mongo不是mango(芒果),这个词是从humongous中截取出来的,其野心不言而明,直指海量数据存储. 和其他很多NoSQL不太一样,MongoDB背后有一个专门的商业公司在提供支持和推广,有点类似MySQL AB的模式.

MongoDB 索引

- - 博客园_首页
索引是用来加快查询的,数据库索引与数据的索引类似,有了索引就不需要翻遍整本书,数据库可以直接在索引中查找,. 使得查询速度很快,在索引中找到条目后,就可以直接跳转到目标文档的位置.. 要掌握如何为查询配置最佳索引会有些难度.. MongoDB索引几乎和关系型数据库的索引一样.绝大数优化关系型数据库索引的技巧同样适用于MongoDB..

MongoDB sql操作

- - 数据库 - ITeye博客
1.  基本查询:. 下面的示例等同于SQL语句的where name = "stephen" and age = 35.      --返回指定的文档键值对. 下面的示例将只是返回name和age键值对.      --指定不返回的文档键值对. 下面的示例将返回除name之外的所有键值对.

MongoDB Shareding部署

- - 开源小站
几年前写过 MongoDB的Sharding和replication. 其实现在看起来Replication还是可以,Sharding的部分有点过于简单了. 于是现在重新补充一下,至少也更新下,毕竟现在的MongoDB已经到了2.6,于当时的2.2还是有所差异的. 正常的情况下,应该是有6台主机实现一个比较像样的MongoDB Sharding集群,它们分别是mongos /router1台,config 3台,shard 2台.