MongoDB 聚合操作

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介:

在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),%(modsubtract)。

对于字符数据,substr:[expr,start,length]concat:[expr1,expr2,,,exprn],用于将表达式连接在一起;toLowerexprtoUpper:expr用于返回expr的小写或大写形式。

2.4 分组操作

使用groupdocgroup将分组字段的值相同的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])

参考doc:

Aggregation

作者悦光阴
本文版权归作者和博客园所有,欢迎转载,但未经作者同意,必须保留此段声明,且在文章页面醒目位置显示原文连接,否则保留追究法律责任的权利。
分类: MongoDB






本文转自悦光阴博客园博客,原文链接:http://www.cnblogs.com/ljhdo/p/5019837.html,如需转载请自行联系原作者
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
打赏
0
0
0
0
20
分享
相关文章
|
5月前
|
MongoDB 聚合
10月更文挑战第17天
34 4
|
6月前
|
MongoDB聚合操作总结
这篇文章总结了MongoDB中聚合操作的作用、方法、常见聚合表达式以及聚合管道的概念和常用操作符,以及SQL与MongoDB聚合操作的对应关系。
110 2
MongoDB聚合操作总结
python3操作MongoDB的crud以及聚合案例,代码可直接运行(python经典编程案例)
这篇文章提供了使用Python操作MongoDB数据库进行CRUD(创建、读取、更新、删除)操作的详细代码示例,以及如何执行聚合查询的案例。
52 6
深入探索MongoDB的聚合操作
【10月更文挑战第13天】
94 0
WPF与DevOps的完美邂逅:从Jenkins配置到自动化部署,全流程解析持续集成与持续交付的最佳实践
【8月更文挑战第31天】WPF与DevOps的结合开启了软件生命周期管理的新篇章。通过Jenkins等CI/CD工具,实现从代码提交到自动构建、测试及部署的全流程自动化。本文详细介绍了如何配置Jenkins来管理WPF项目的构建任务,确保每次代码提交都能触发自动化流程,提升开发效率和代码质量。这一方法不仅简化了开发流程,还加强了团队协作,是WPF开发者拥抱DevOps文化的理想指南。
140 1
【超实用攻略】MongoDB 聚合框架:从入门到精通,带你解锁数据处理新姿势!
【8月更文挑战第24天】MongoDB是一款以其灵活性和高性能闻名的NoSQL数据库。其强大的聚合框架采用管道式处理,允许用户定义多个数据处理阶段如过滤、分组等。本文通过示例数据库`orders`和`products`,演示如何利用聚合框架计算各产品的总销售额。示例代码展示了使用`lookupunwind`打平数组及`$group`按产品ID分组并计算总销售额的过程。这突显了聚合框架处理复杂查询的强大能力,是进行数据分析和报表生成的理想选择。
92 3
04 MongoDB各种查询操作 以及聚合操作总结
文章全面总结了MongoDB中的查询操作及聚合操作,包括基本查询、条件筛选、排序以及聚合管道的使用方法和实例。
166 0
MongoDB存储过程实战:聚合框架、脚本、最佳实践,一文全掌握!
【8月更文挑战第24天】MongoDB是一款备受欢迎的文档型NoSQL数据库,以灵活的数据模型和强大功能著称。尽管其存储过程支持不如传统关系型数据库,本文深入探讨了MongoDB在此方面的最佳实践。包括利用聚合框架处理复杂业务逻辑、封装业务逻辑提高复用性、运用JavaScript脚本实现类似存储过程的功能以及考虑集成其他工具提升数据处理能力。通过示例代码展示如何创建订单处理集合并定义验证规则,虽未直接实现存储过程,但有效地演示了如何借助JavaScript脚本处理业务逻辑,为开发者提供更多实用指导。
125 2
【MongoDB大神级操作】揭秘聚合框架,让你的数据处理能力瞬间飙升,秒变数据界的超级英雄!
【8月更文挑战第24天】MongoDB是一款备受欢迎的非关系型数据库,以其灵活的文档模型和出色的可扩展性著称。其聚合框架尤其亮眼,能高效地对数据库中的数据执行复杂的转换与聚合操作,无需将数据导出到应用端处理,极大提升了数据处理的效率与灵活性。例如,在一个大型电商数据库中,聚合框架能轻松分析出最热卖的商品或特定时段内某类别商品的销售总额。通过一系列管道操作,如unwindgroup等,可以对数据进行逐步处理并得到最终结果,同时还支持过滤、排序、分页等多种操作,极大地丰富了数据处理的能力,成为进行数据分析、报表生成及复杂业务逻辑实现的强大工具。
110 2
“WPF与DevOps深度融合:从Jenkins配置到自动化部署全流程解析,助你实现持续集成与持续交付的无缝衔接”
【8月更文挑战第31天】本文详细介绍如何在Windows Presentation Foundation(WPF)项目中应用DevOps实践,实现自动化部署与持续集成。通过具体代码示例和步骤指导,介绍选择Jenkins作为CI/CD工具,结合Git进行源码管理,配置构建任务、触发器、环境、构建步骤、测试及部署等环节,显著提升开发效率和代码质量。
132 0