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

一,使用 Pipeline 方式计算聚合

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

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

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

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

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

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

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

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

2.1 选择字段

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

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

2.2 对字段重命名,产生新的字段

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

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

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

2.3 派生字段

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

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

db.foo.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的小写或大写形式。

2.4 分组操作

使用$group将doc按照特定的字段的值进行分组,$group将分组字段的值相同的doc作为一个分组进行聚合计算。如果没有$group 管道符,那么所有doc作为一个分组。对每一个分组,都能根据业务逻辑需要计算特定的聚合值。分组操作和排序操作都是非流式的运算符,流式运算符是指:只要有新doc进入,就可以对doc进行处理,而非流式运算符是指:必须等收到所有的文档之后,才能对文档进行处理。分组运算符的处理方式是等接收到所有的doc之后,才能对doc进行分组,然后将各个分组发送给pipeline的下一个运算符进行处理。

示例,按照age进行分组,统计每个分组中的doc数量

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

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

db.foo.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}}}
)

对每个分组进行聚合运算,count字段是计算每个分组中doc的数量,idxTotal字段是计算每个分组中idx字段值的加和,idxMax字段是计算每个分组中idx字段值的最大值,idxFirst是计算每个分组中第一个idx 字段的值,不一定是最小的。

 
db.foo.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},
idxTotal:{$sum:"$idx"}},
idxMax:{$max:"$idx"},
idxFirst:{$first:"$idx"}
}
}
)
 

2.5,sort操作,limit操作 和 skip操作
对聚合操作的结果进行排序,然后跳过前10个doc,取剩余结果集的前10个doc。

 
db.foo.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},
idxTotal:{$sum:"$idx"}},
idxMax:{$max:"$idx"},
idxFirst:{$first:"$idx"}
}
},
{$sort:{age:-1}},
{$skip:10},
{$limit:10}
)
 

二,使用MapReduce 方式计算聚合
MapReduce 能够计算非常复杂的聚合逻辑,非常灵活,但是,MapReduce非常慢,不应该用于实时的数据分析中。MapReduce能够在多台Server上并行执行,每台Server只负责完成一部分wordload,最后将wordload发送到Master Server上合并,计算出最终的结果集,返回客户端。

MapReduce分为两个阶段:Map和Reduce,举个例子说明,有10节车厢,统计这10节车厢中男生和女生的数量。串行方式一节一节车厢的统计,直到统计完全部车厢中的人数:男50人,女40人。

使用MapReduce方式的思路是:每个车厢派一个人去统计,每个人返回一个doc,例如,keyN:{female:num1,male:num2},keyN是车厢编号,在同一时间,有10个人在同时工作,每个人只完成全部workload的10%,很快,返回10个doc,从Key1到Key10,只需要将这10个doc中 femal 和 male分别加和到一起,就是全部车厢的人数:男50人,女40人。

使用MapReduce方式计算聚合,主要分为三步:Map,Shuffle(拼凑)和Reduce,Map和Reduce需要显式定义,shuffle由MongoDB来实现。

  • Map:将操作映射到每个doc,产生Key和Value,例如,Map一个doc,产生(female,{count:1}),female是Key,value是{count:1}
  • Shuffle:按照Key进行分组,并将key相同的Value组合成数组,例如,产生(female,[{count:1},{count:1},{count:1},{count:1},,,,,])
  • Reduce:把Value数组化简为单值,例如,产生(femal,{count:21})

使用MapReduce进行聚合运算的最佳方式是聚合运算的结果能够加到一起,例如,求最大值/最小值,sum,平均值(转换为计算每台Server的 总和sum1,sum2,,,sumN 与 num1,num2,,numN,平均值avg=(sum1+sum2+,,,+sumN)/(num1+num2+,,+numN))等。

示例,使用MapReduce模拟Count,统计集合中的doc的数量

step1,定义Map函数和reduce函数

对于每个doc,直接返回key 和 一个doc:{count:1}

 
map=function (){
for(var key in this)
{
emit(key,{count:1});
}
} reduce=function (key,emits){
total=0;
for(var i in emits){
total+=emits[i].count;
}
return {"count":total};
}
 

step2,执行MapReduce运算
在集合 foo上执行MapReduce运算,返回mr 对象

 
mr=db.runCommand(
{
"mapreduce":"foo",
"map":map,
"reduce":reduce,
out:"Count Doc"
})
 

step3,查看MapReduce计算的结果

db[mr.result].find()

示例2,统计集合foo中不同age的数量

step1,定义Map 和 Reduce函数

Map函数的作用是对每个doc进行一次映射,返回age 和 {count:1};

经过Shuffle,每个age都有一个列表:[{count:1},{count:1},{count:1},{count:1},,,,,],有多少个不同的age,MongoDB都会调用多少次Reduce函数,每次调用时,Key值是不同的。

Reduce函数的作用:对MongoDB的一次调用,对age对应的列表进行聚合运算。

 
map=function ()
{
emit(this.age,{count:1});
} reduce= function (key,emits)
{
total=0;
for(var i in emits)
{
total+=emits[i].count;
} return {"age":key,count:total};
}
 

step2,执行MapReduce聚合运算

 
mr=db.runCommand(
{
"mapreduce":"foo",
"map":map,
"reduce":reduce,
out:"Count Doc"
})
 

step3,查看聚合运算的结果

db[mr.result].find()

示例3,研究reduce函数的特性

reduce函数具有累加的特性,通过多次调用,能够产生最终的累加值,例如,以下reduce函数对于任意一个特定的key,reduce都能计算key的数量

reduce= function (key,emits)
{
total=0;
for(var i in emits)
{
total+=emits[i].count;
} return {"key":key,count:total};
}

调用示例:传递的Key是相同的,都是“x”,每个emits都是一个数组,反复调用reduce函数,最终获得key的累加值。

r1=reduce("x",[{count:1},{count:2}])
r2=reduce("x",[{count:3},{count:5}])
r3=reduce("x",[r1,r2])

MongoDB 聚合操作(转)的更多相关文章

  1. MongoDB 聚合操作

    在MongoDB中,有两种方式计算聚合:Pipeline 和 MapReduce.Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复 ...

  2. mongodb聚合操作

    1. mongodb的聚合是什么 聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组.过滤等功能,然后经过一系列的处理 ...

  3. mongodb聚合查询-aggregate

    Mongodb-aggregate 在工作中经常遇到一些mongodb的聚合操作,和mysql对比起来,mongo存储的可以是复杂的类型,比如数组,字典等mysql不善于处理的文档型结构,但是mong ...

  4. MongoDB学习笔记——聚合操作之聚合管道(Aggregation Pipeline)

    MongoDB聚合管道 使用聚合管道可以对集合中的文档进行变换和组合. 管道是由一个个功能节点组成的,这些节点用管道操作符来进行表示.聚合管道以一个集合中的所有文档作为开始,然后这些文档从一个操作节点 ...

  5. MongoDB 基本操作和聚合操作

    一 . MongoDB 基本操作 基本操作可以简单分为查询.插入.更新.删除. 1 文档查询 作用 MySQL SQL  MongoDB  所有记录  SELECT * FROM users;  db ...

  6. Yii2的mongodb的聚合操作

    最近项目使用到mongodb的聚合操作,但是yii文档中对这方面资料较少,记录下 $where['created_time'] = ['$gt' => "$start_date_str ...

  7. MongoDB中的聚合操作

    根据MongoDB的文档描述,在MongoDB的聚合操作中,有以下五个聚合命令. 其中,count.distinct和group会提供很基本的功能,至于其他的高级聚合功能(sum.average.ma ...

  8. MongoDB的聚合操作以及与Python的交互

    上一篇主要介绍了MongoDB的基本操作,包括创建.插入.保存.更新和查询等,链接为MongoDB基本操作. 在本文中主要介绍MongoDB的聚合以及与Python的交互. MongoDB聚合 什么是 ...

  9. MongoDB入门---聚合操作&管道操作符&索引的使用

    经过前段时间的学习呢,我们对MongoDB有了一个大概的了解,接下来就要开始使用稍稍深入一点的东西了,首先呢,就是MongoDB中的聚合函数,跟mysql中的count等函数差不多.话不多说哈,我们先 ...

随机推荐

  1. Python 打印当前文件相对路径和绝对路径

    一.打印相对路径 print(__file__) 二.打印绝对路径 import os print(os.path.abspath(__file__)) 三.打印文件名 import os print ...

  2. Ajax提交form表单内容和文件(jQuery.form.js)

    jQuery官网是这样介绍form.js A simple way to AJAX-ify any form on your page; with file upload and progress s ...

  3. 【Java】 剑指offer(50-1) 字符串中第一个只出现一次的字符

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 在字符串中找出第一个只出现一次的字符.如输入"abacc ...

  4. 【Java】 剑指offer(57-2) 为s的连续正数序列

      本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 输入一个正数s,打印出所有和为s的连续正数序列(至少含有两个数 ...

  5. kali 解决Metasploit拿到shell后显示中文乱码问题

    拿到对方shell后显示的问题如下: 中文乱码解决: chcp 65001 然后 上传下载文件

  6. 12,EasyNetQ-自动订阅

    EasyNetQ自v0.7.1.30附带一个简单的AutoSubscriber. 您可以使用它轻松扫描实现接口IConsume <T>或IConsumeAsync <T>的类的 ...

  7. XenServer日志清理方法

    服务器使用时间长了,XenServer产生了很多日志,甚至有些人因为日志占满了空间, 导致系统出现问题:xapi崩溃,或者系统卡死,重启也无效. 所以我们要时常看看日志是否占的空间的,清理下日志先查看 ...

  8. php页面静态化,ob缓存方法

    <?php ob_start();//开启缓存 //要生成静态网页的内容开始 ?> 中间的html代码 <?php //要生成静态网页的内容结束 //把生成的静态内容保存到文件,而不 ...

  9. python——设计模式

    设计模式是什么? 设计模式是经过总结.优化的,对我们经常会碰到的一些编程问题的可重用解决方案.一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码.反之,设计模式更为高级,它是一种必须在特定情 ...

  10. 基于Grunt构建一个JavaScript库

    现在公认的JavaScript典型项目需要运行单元测试,合并压缩.有些还会使用代码生成器,代码样式检查或其他构建工具. Grunt.js是一个开源工具,可以帮助你完成上面的所有步骤.它非常容易扩展,并 ...