MongoDB(4): 聚合框架

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

一、简介

MongoDB的聚合框架,主要用来对集合中的文档进行变换和组合,从而对数据进行分析以加以利用。


聚合框架的基本思路是:

 采用多个构件来创建一个管道,用于对一连串的文档进行处理。

这些构件包括:

 筛选(filtering)、投影(projecting)、分组(grouping)、排序(sorting)、限制(limiting)和跳过(skipping)。


使用聚合框架的方式:

 db.集合.aggregate(构件1,构件2…)

注意:由于聚合的结果要返回到客户端,因此聚合结果必须限制在16M以内,这是MongoDB支持的最大响应消息的大小。


二、使用例子

2.1、准备样例数据

1
2
3
4
5
for (var i=0;i<100;i++){
     for (var j=0;j<4;j++){
         db.scores.insert({ "studentId" : "s" +i, "course" : "课程" +j, "score" :Math.random()*100});
     }
}

2.2、找出考80分以上的课程门数最多的3个学生

步骤:

 1:找到所有考了80分以上的学生,不区分课程

1
> db.scores.aggregate({ "$match" :{ "score" :{ $gte :80}}});

2:将每个学生的名字投影出来

1
> db.scores.aggregate({ "$match" :{ "score" :{ $gte :80}}},{ $project :{ "studentId" :1}});

3:对学生的名字排序,某个学生的名字出现一次,就给他加1

1
> db.scores.aggregate({ "$match" :{ "score" :{ $gte :80}}},{ $project :{ "studentId" :1}},{ $group :{ "_id" : "$studentId" , "count" :{ $sum :1}}});

4:对结果集按照count进行降序排列

1
> db.scores.aggregate({ "$match" :{ "score" :{ $gte :80}}},{ $project :{ "studentId" :1}},{ $group :{ "_id" : "$studentId" , "count" :{ $sum :1}}},{ "$sort" :{ "count" :-1}});

5:返回前面的3条数据

1
db.scores.aggregate({ "$match" :{ "score" :{ $gte :80}}},{ $project :{ "studentId" :1}},{ $group :{ "_id" : "$studentId" , "count" :{ $sum :1}}},{ "$sort" :{ "count" :-1}},{ "$limit" :3});

wKioL1kwuxegZ-SvAAAeJMx4Vz8704.jpg三、管道操作符

每个操作符接受一系列的文档,对这些文档做相应的处理,然后把转换后的文档作为结果传递给下一个操作符。最后一个操作符会将结果返回。

不同的管道操作符,可以按照任意顺序,任意个数组合在一起使用。

3.1、筛选命令$match

用于对文档集合进行筛选,里面可以使用所有常规的查询操作符。通常会放置在管道最前面的位置,理由如下:

 1:快速将不需要的文档过滤,减少后续操作的数据量

 2:在投影和分组之前做筛选,查询可以使用索引

3.2、投影命令$project

用来从文档中提取字段,可以指定包含和排除字段,也可以重命名字段。比如要将studentId改为sid,如下:

db.scores.aggregate({"$project":{"sid":"$studentId"}})

管道操作符还可以使用表达式,以满足更复杂的需求。

管道操作符$project的数学表达式:

比如给成绩集体加20分,如下:

1
> db.scores.aggregate({ "$project" :{ "studentId" :1, "newScore" :{ $add :[ "$score" ,20]}}});

支持的操作符和相应语法:

 1:$add : [expr1[,expr2,…exprn]]

 2:$subtract:[expr1,expr2]

 3:$multiply:[expr1[,expr2,…exprn]]

 4:$divice:[expr1,expr2]

 5:$mod:[expr1,expr2]

管道操作符$project的日期表达式:

聚合框架包含了一些用于提取日期信息的表达式,如下:

$year、$month、$week、$dayOfMonth、$dayOfWeek、$dayOfYear、$hour、$minute、$second 。

注意:这些只能操作日期型的字段,不能操作数据,使用示例:

{"$project":{"opeDay":{"$dayOfMonth":"$recoredTime"}}}

管道操作符$project的字符串表达式:

1:$substr : [expr,开始位置,要取的字节个数]

2:$concat:[expr1[,expr2,…exprn]]

3:$toLower:expr

4:$toUpper:expr

例如:{"$project":{"sid":{$concat:["$studentId","cc"]}}}

管道操作符$project的逻辑表达式:

1:$cmp:[expr1,expr2] :比较两个表达式,0表示相等,正数前面的大,负数后面的大

2:$strcasecmp:[string1,string2] :比较两个字符串,区分大小写,只对由罗马字符组成的字符串有效

3:$eq、$ne、$gt、$gte、$lt、$lte :[expr1,expr2]

4:$and、$or、$not

5:$cond:[booleanExpr,trueExpr,falseExpr]:如果boolean表达式为true,返回true表达式,否则返回false表达式

6:$ifNull:[expr,otherExpr]:如果expr为null,返回otherExpr,否则返回expr

例如:db.scores.aggregate({"$project":{"newScore":{$cmp:["$studentId","sss"]}}})

3.3、分组命令$group

用来将文档依据特定字段的不同值进行分组。选定了分组字段过后,就可以把这些字段传递给$group函数的“_id”字段了。例如:

 db.scores.aggregate({“$group”:{“_id”:“$studentId”}}); 

 或者是

 db.scores.aggregate({"$group":{"_id":{"sid":"$studentId","score":"$score"}}});

$group支持的操作符:

1:$sum:value :对于每个文档,将value与计算结果相加

2:$avg:value :返回每个分组的平均值

3:$max:expr :返回分组内的最大值

4:$min:expr :返回分组内的最小值

5:$first:expr :返回分组的第一个值,忽略其他的值,一般只有排序后,明确知道数据顺序的时候,这个操作才有意义

6:$last:expr :与上面一个相反,返回分组的最后一个值

7:$addToSet:expr :如果当前数组中不包含expr,那就将它加入到数组中

8:$push:expr:把expr加入到数组中

3.4、拆分命令$unwind

用来把数组中的每个值拆分成为单独的文档。

wKioL1kwx9LBIKpVAAAlUzIGSsA235.png

3.5、排序命令$sort

可以根据任何字段进行排序,与普通查询中的语法相同。如果要对大量的文档进行排序,强烈建议在管道的第一个阶段进行排序,这时可以使用索引。

3.6、常见的聚合函数

1:count:用于返回集合中文档的数量

2:distinct:找出给定键的所有不同值,使用时必须指定集合和键,例如:

db.runCommand({"distinct":"users","key":"userId"});

四、MapReduce

在MongoDB的聚合框架中,还可以使用MapReduce,它非常强大和灵活,但具有一定的复杂性,专门用于实现一些复杂的聚合功能。

MongoDB中的MapReduce使用JavaScript来作为查询语言,因此能表达任意的逻辑,但是它运行非常慢,不应该用在实时的数据分析中。

4.1、MapReduce的HelloWorld

实现的功能,找出集合中所有的键,并统计每个键出现的次数。

1:Map函数使用emit函数来返回要处理的值,示例如下:

1
2
3
4
5
var  map  = function(){
     for (var key in this){
         emit(key,{count:1});
     }
}

this表示对当前文档的引用。

2:reduce函数需要处理Map阶段或者是前一个reduce的数据,因此reduce返回的文档必须要能作为reduce的第二个参数的一个元素,示例如下:

1
2
3
4
5
6
7
var reduce = function(key,emits){
     var total = 0;
     for (var i in emits){
         total += emits[i].count;
     }
     return  { "count" :total};
};

3:运行MapReduce,示例如下:

1
> var mr =db.runCommand({ "mapreduce" : "scores" , "map" : map , "reduce" :reduce, "out" : "mrout" });

说明:scores是集合名,map是map函数,reduce是reduce函数,mrout是输出的变量名

4:查询最终的结果,示例如下:

1
db.mrout.find();

wKioL1kwzW2SYV4VAAAan6Ls3PY486.png

还可以改变一下,比如统计studentId中值,以及每个值出现的次数,就可以如下操作:

1:修改map函数,示例如下:

1
2
3
var  map  = function(){
     emit(this.studentId,{count:1});
};

2:reduce函数不用改

3:重新执行

1
db.runCommand({ "mapreduce" : "scores" , "map" : map , "reduce" :reduce, "out" : "mrout" });

4:查看最终结果

1
db.mrout.find();

wKioL1kwzrqgOY29AAA2gVwzOA0595.png

4.2、更多MapReduce可选的键

1:finalize:function :可以将reduce的结果发送到finalize,这是整个处理的最后一步

2:keeptemp:boolean :是否在连接关闭的时候,保存临时结果集合

3:query:document :在发送给map前对文档进行过滤

4:sort:document :在发送给map前对文档进行排序

5:limit:integer :发往map函数的文档数量上限

6:scope:document :可以在javascript中使用的变量

7:verbose:boolean :是否记录详细的服务器日志


示例:

1
2
3
4
5
6
var query = { "studentId" :{ "$lt" : "s2" }}
var  sort  = { "studentId" :1};
var finalize = function(key,value){
     return  { "mykey" :key, "myV" :value};
};
var mr =db.runCommand({ "mapreduce" : "scores" , "map" : map , "reduce" :reduce, "out" : "mrout" , "query" :query, "sort" : sort , "limit" :2, "finalize" :finalize});

wKioL1kw0R2wOrL8AAAKXqTSdok429.png

五、聚合命令group

用来对集合进行分组,分组过后,再对每一个分组内的文档进行聚合。

比如要对studentId进行分组,找到每个学生最高的分数,可以如下步骤进行:

1
2
3
4
5
6
7
8
9
10
db.runCommand({ "group" :{
     "ns" : "scores" ,
     "key" :{ "studentId" :1},
     "initial" :{ "score" :0},
     "$reduce" :function(doc,prev){
         if (doc.score > prev.score){
             prev.score = doc.score;
         }
     }
}});

ns:指定要分组的集合

key:指定分组的键

initial:每一组的reduce函数调用的时候,在开头的时候调用一次,以做初始化

$reduce:在每组中的每个文档上执行,系统会自动传入两个参数,doc是当前处理的文档,prev是本组前一次执行的结果文档


你还可以在group的时候添加条件,就是加入condition,示例如:

1
2
3
4
5
6
7
8
9
10
11
db.runCommand({ "group" :{
     "ns" : "scores" ,
     "key" :{ "studentId" :1},
     "initial" :{ "score" :0},
     "$reduce" :function(doc,prev){
         if (doc.score > prev.score){
             prev.score = doc.score;
         }
     }
     , "condition" :{ "studentId" :{ $lt : "s2" }}
}});


同样可以使用finalizer来对reduce的结果进行最后的处理,比如要求每个学生的平均分,就可以先按照studentId分组,求出一个总的分数来,然后在finalizer里面,求平均分:

1
2
3
4
5
6
7
8
9
10
11
12
db.runCommand({ "group" :{
     "ns" : "scores" ,
     "key" :{ "studentId" :1},
     "initial" :{ "total" :0},
     "$reduce" :function(doc,prev){
         prev.total += doc.score;
     },
     "condition" :{ "studentId" :{ "$lt" : "s2" }},
     "finalize" :function(prev){
         prev.avg = prev.total/3;
     }
}});

注意:finalize是只在每组结果返回给用户前调用一次,也就是每组结果只调用一次


对于分组的key较为复杂的时候,还可以采用函数来做为键,比如让键不区分大小下,就可以如下定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.runCommand({ "group" :{
     "ns" : "scores" ,
     $keyf :function(doc){
         return  {studentId:doc.studentId.toLowerCase()};
     },
     "initial" :{ "total" :0},
     "$reduce" :function(doc,prev){
         prev.total += doc.score;
     },
     "condition" :{ "$or" :[{ "studentId" :{ "$lt" : "s2" }},{ "studentId" : "S0" }]},
     "finalize" :function(prev){
         prev.avg = prev.total/3;
     }
}});

注意:要使用$keyf来定义函数作为键,另外一定要返回对象的格式

本文转自我爱大金子博客51CTO博客,原文链接http://blog.51cto.com/1754966750/1931674如需转载请自行联系原作者


我爱大金子

相关实践学习
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
相关文章
|
9天前
|
SQL NoSQL Unix
MongoDB 聚合
10月更文挑战第17天
14 4
|
2月前
|
SQL NoSQL Unix
MongoDB聚合操作总结
这篇文章总结了MongoDB中聚合操作的作用、方法、常见聚合表达式以及聚合管道的概念和常用操作符,以及SQL与MongoDB聚合操作的对应关系。
37 2
MongoDB聚合操作总结
|
9天前
|
SQL NoSQL 数据处理
深入探索MongoDB的聚合操作
【10月更文挑战第13天】
8 0
|
2月前
|
NoSQL MongoDB 数据库
python3操作MongoDB的crud以及聚合案例,代码可直接运行(python经典编程案例)
这篇文章提供了使用Python操作MongoDB数据库进行CRUD(创建、读取、更新、删除)操作的详细代码示例,以及如何执行聚合查询的案例。
28 6
|
2月前
|
SQL NoSQL JavaScript
04 MongoDB各种查询操作 以及聚合操作总结
文章全面总结了MongoDB中的查询操作及聚合操作,包括基本查询、条件筛选、排序以及聚合管道的使用方法和实例。
65 0
|
3月前
|
持续交付 jenkins Devops
WPF与DevOps的完美邂逅:从Jenkins配置到自动化部署,全流程解析持续集成与持续交付的最佳实践
【8月更文挑战第31天】WPF与DevOps的结合开启了软件生命周期管理的新篇章。通过Jenkins等CI/CD工具,实现从代码提交到自动构建、测试及部署的全流程自动化。本文详细介绍了如何配置Jenkins来管理WPF项目的构建任务,确保每次代码提交都能触发自动化流程,提升开发效率和代码质量。这一方法不仅简化了开发流程,还加强了团队协作,是WPF开发者拥抱DevOps文化的理想指南。
68 1
|
3月前
|
NoSQL BI 数据处理
【超实用攻略】MongoDB 聚合框架:从入门到精通,带你解锁数据处理新姿势!
【8月更文挑战第24天】MongoDB是一款以其灵活性和高性能闻名的NoSQL数据库。其强大的聚合框架采用管道式处理,允许用户定义多个数据处理阶段如过滤、分组等。本文通过示例数据库`orders`和`products`,演示如何利用聚合框架计算各产品的总销售额。示例代码展示了使用`$lookup`连接两集合、`$unwind`打平数组及`$group`按产品ID分组并计算总销售额的过程。这突显了聚合框架处理复杂查询的强大能力,是进行数据分析和报表生成的理想选择。
40 3
|
3月前
|
存储 NoSQL JavaScript
MongoDB存储过程实战:聚合框架、脚本、最佳实践,一文全掌握!
【8月更文挑战第24天】MongoDB是一款备受欢迎的文档型NoSQL数据库,以灵活的数据模型和强大功能著称。尽管其存储过程支持不如传统关系型数据库,本文深入探讨了MongoDB在此方面的最佳实践。包括利用聚合框架处理复杂业务逻辑、封装业务逻辑提高复用性、运用JavaScript脚本实现类似存储过程的功能以及考虑集成其他工具提升数据处理能力。通过示例代码展示如何创建订单处理集合并定义验证规则,虽未直接实现存储过程,但有效地演示了如何借助JavaScript脚本处理业务逻辑,为开发者提供更多实用指导。
60 2
|
3月前
|
存储 NoSQL 数据处理
【MongoDB大神级操作】揭秘聚合框架,让你的数据处理能力瞬间飙升,秒变数据界的超级英雄!
【8月更文挑战第24天】MongoDB是一款备受欢迎的非关系型数据库,以其灵活的文档模型和出色的可扩展性著称。其聚合框架尤其亮眼,能高效地对数据库中的数据执行复杂的转换与聚合操作,无需将数据导出到应用端处理,极大提升了数据处理的效率与灵活性。例如,在一个大型电商数据库中,聚合框架能轻松分析出最热卖的商品或特定时段内某类别商品的销售总额。通过一系列管道操作,如$unwind、$group等,可以对数据进行逐步处理并得到最终结果,同时还支持过滤、排序、分页等多种操作,极大地丰富了数据处理的能力,成为进行数据分析、报表生成及复杂业务逻辑实现的强大工具。
68 2
|
3月前
|
持续交付 jenkins C#
“WPF与DevOps深度融合:从Jenkins配置到自动化部署全流程解析,助你实现持续集成与持续交付的无缝衔接”
【8月更文挑战第31天】本文详细介绍如何在Windows Presentation Foundation(WPF)项目中应用DevOps实践,实现自动化部署与持续集成。通过具体代码示例和步骤指导,介绍选择Jenkins作为CI/CD工具,结合Git进行源码管理,配置构建任务、触发器、环境、构建步骤、测试及部署等环节,显著提升开发效率和代码质量。
69 0