在MongoDB中,有两种方式计算聚合:Pipeline 和 MapReduce。Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑。MongoDB不允许Pipeline的单个聚合操作占用过多的系统内存,如果一个聚合操作消耗20%以上的内存,那么MongoDB直接停止操作,并向客户端输出错误消息。



一,使用 Pipeline 方式计算聚合

Pipeline 方式使用db.collection.aggregate()函数进行聚合运算,运算速度较快,操作简单,但是,Pipeline方式有两个限制:单个聚合操作消耗的内存不能超过20%,聚合操作返回的结果集必须限制在16MB以内。

创建示例数据,在集合 c5中插入1000条doc,每个doc中有三个field:idx,name 和 age。

for(i=0;i<10000;i++)
{ 
  db.c5.insert({"idx":i,name:"user "+i,age:i%90});
};

1 、使用$match 管道符过滤collection中doc,使符合条件的doc进入pipeline,能够减少聚合操作消耗的内存,提高聚合的效率。

db.c5.aggregate({$match:{age:{$lte:25}}});

2 、使用$project 管道符,使用doc中的部分field进入下级pipeline

db.c5.aggregate(
{$match:{age:{$lte:25}}}, 
{$project:{age:1,idx:1,_id:0}} 
);

project 管道符的作用是选择字段,重命名字段,派生字段。

在$project 管道符中,field:1/0,表示选择/不选择 field;将无用的字段从pipeline中过滤掉,能够减少聚合操作对内存的消耗。

3 、对字段重命名,产生新的字段

引用符$,格式是:"$field",表示引用doc中 field 的值,如果要引用内嵌 doc中的字段,使用 "$field1.filed2",表示引用内嵌文档field1中的字段:field2的值。

示例,新建一个field:preIdx,其值和idx 字段的值是相同的。

db.c5.aggregate(
{$match:{age:{$lte:25}}}, 
{$project:{age:1,"preIdx":"$idx",idx:1,"_id":0}} 
);

4.派生字段

在$project中,对字段进行计算,根据doc中的字段值和表达式,派生一个新的字段。

示例,preIdx是根据当前doc的idx 减1 得到的

db.c5.aggregate(
{$match:{age:{$lte:25}}}, 
{$project:
     {
    age:1,
    "preIdx":{$subtract:["$idx",1]},
    idx:1,
    "_id":0}
     } 
);

在$project 执行算术运算的操作符:+($add),*($multiply),/($divide),%($mod),-($subtract)。

对于字符数据,$substr:[expr,start,length]用于求子字符串;

                       $concat:[expr1,expr2,,,exprn],用于将表达式连接在一起;

                       $toLower:expr 和 $toUpper:expr用于返回expr的小写或大写形式。

5.分组操作

使用$group将doc按照特定的字段的值进行分组,$group将分组字段的值相同的doc作为一个分组进行聚合计算。如果没有$group 管道符,那么所有doc作为一个分组。对每一个分组,都能根据业务逻辑需要计算特定的聚合值。

分组操作和排序操作都是非流式的运算符

流式运算符是指:只要有新doc进入,就可以对doc进行处理,

非流式运算符是指:必须等收到所有的文档之后,才能对文档进行处理。

分组运算符的处理方式是等接收到所有的doc之后,才能对doc进行分组,然后将各个分组发送给pipeline的下一个运算符进行处理。

//相当于 select age,count(*) where age <=25 group by age;
db.c5.aggregate(
{$match:{age:{$lte:25}}}, 
{$project:{age:1,"preIdx":{$subtract:["$idx",1]},idx:1,"_id":0}} ,
{$group:{"_id":"$age",count:{$sum:1}}}
);

//对于分组后的数量处理 //相当于 select age,count(*) where age <=25 group by age having count(*) >= 112;
db.c5.aggregate(
{$match:{age:{$lte:25}}}, 
{$project:{age:1,"preIdx":{$subtract:["$idx",1]},idx:1,"_id":0}} ,
{$group:{"_id":"$age",count:{$sum:1}}},
{$match:{count:{$gte:112}}} //分组后的数量大于等于112的
);

如果分组字段有多个,按照 age 和 age2 进行分组,这样做仅仅是为了演示,在实际的产品环境中,可以使用更多的字段用来分组。

db.c5.aggregate(
{$match:{age:{$lte:25}}}, 
{$project:{age:1,"preIdx":{$subtract:["$idx",1]},idx:1,"_id":0}} ,
{$group:{"_id":{age:"$age",age2:"$age"},count:{$sum:1}}}
)

 



二、参考

http://www.runoob.com/mongodb/mongodb-aggregate.html