MongoDB(4): 聚合框架

一、简介

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

聚合框架的基本思路是:

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

这些构件包括:

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

使用聚合框架的方式:

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

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

二、使用例子

2.1、准备样例数据

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分以上的学生,不区分课程

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

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

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

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

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

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

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

5:返回前面的3条数据

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

三、管道操作符

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

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

3.1、筛选命令$match

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

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

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

3.2、投影命令$project

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

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

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

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

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

> 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

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

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函数来返回要处理的值,示例如下:

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

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

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

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

3:运行MapReduce,示例如下:

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

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

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

db.mrout.find();

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

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

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

2:reduce函数不用改

3:重新执行

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

4:查看最终结果

db.mrout.find();

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 :是否记录详细的服务器日志

示例:

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});

五、聚合命令group

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

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

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,示例如:

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里面,求平均分:

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较为复杂的时候,还可以采用函数来做为键,比如让键不区分大小下,就可以如下定义:

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来定义函数作为键,另外一定要返回对象的格式

时间: 2024-11-06 19:30:15

MongoDB(4): 聚合框架的相关文章

【MongoDB】MongoDB之聚合框架(一)

题记: 还有3天2014年结束了,这个月的计划除了总结Mongodb外其他计划都已经完成了.最近由于懒惰对于mongodb的研究暂停了好长时间.在最后的三天里,对于mongodb的研究做以总结和梳理: 一.基本概念 对于聚合框架,我在官方文档(http://docs.mongodb.org/manual/core/aggregation-introduction/)找到对它的介绍.翻译大概意思如下: 聚合是处理数据记录并且返回计算结果的操作.mongodb提供了一组强大针对数据集合进行检查和计算

【MongoDB学习笔记30】MongoDB的聚合框架

使用聚合框架可以对集合中的文档进行变换和组合.用多个构件创建一个管道(pipeline),用于对一连串的文档进行处理.这些构件包括: 筛选(filtering) 投射(projecting) 分组(grouping) 排序(sorting) 限制(limiting) 跳过(skipping) 在MongoDB中实际的集合框架,需要将这些操作传给aggregate函数,例如: (1)将文档的name字段投射出来 > db.post.aggregate({$project:{"name&quo

【翻译】MongoDB指南/聚合——聚合管道

[原文地址]https://docs.mongodb.com/manual/ 聚合 聚合操作处理数据记录并返回计算后的结果.聚合操作将多个文档分组,并能对已分组的数据执行一系列操作而返回单一结果.MongoDB提供了三种执行聚合的方式:聚合管道,map-reduce方法和单一目的聚合操作. 聚合管道 MongoDB的聚合框架模型建立在数据处理管道这一概念的基础之上.文档进入多阶段管道中,管道将文档转换为聚合结果.最基本的管道阶段类似于查询过滤器和修改输出文档形式的文档转换器. 其他的管道为分组和

mongodb的高级操作(聚合框架)

group by 查询 不要用java驱动带的group by ,要用2.2版本后的aggregate聚合框架来搞,经过试验速度快一倍 参考 官网:http://docs.mongodb.org/manual/reference/sql-aggregation-comparison/ 实例:http://www.yeetrack.com/?p=649 观看例子前,请先看上面对的2个参考文档,特别是官网的 例子 [java] view plaincopy // 首先利$match筛选出where条

Mongodb的聚合和管道

MongoDB 聚合 MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果. aggregate() 方法 MongoDB中聚合的方法使用aggregate(). 语法 aggregate() 方法的基本语法格式如下所示: >db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION) 注:参数AGGREGATE_OPERATION可以是一个对象(单个处理),也可以是多个对象的数组(管道处理). >

mongodb 分组聚合查询

MongoDB,分组,聚合 使用聚合,db.集合名.aggregate- 而不是find 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数.MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理.管道操作是可以重复的. 每一个操作符(集合)都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作符,对于最后一个操作符,是将结果返回给客户端 //分组(这里制定了分组字段 $+字段名)//这里可以理解为

聚合框架

Pipeline语法简介 MongoDB聚合就是把一系列特殊操作符作用于一个集合.一个操作符就是一个拥有单个属性的JavaScript对象,其属性即操作符名称,其值是一个可选对象: { $name: { /* options */ } } 支持的操作符命名有:$project, $match, $limit, $skip, $unwind, $group, and $sort,它们每个都有其各自的选项集.一系列操作符就称为管道(Pipeline): [{ $project: { /* optio

MongoDB的使用学习之(七)MongoDB的聚合查询(两种方式)附项目源码

先来张在路上-- 此项目是用Maven创建的,没有使用Maven的,自己百度.谷歌去:直接用Junit测试就行,先执行里面的save方法,添加10000条测试数据提供各种聚合查询等. 废话不多说,上干货-- 一.MongoDB数据库的配置(mongodb.xml) 以下是我自己的配置,红色字体请改为自己本机的东东,你说不懂设置端口,不会创建数据库名称,不会配置用户名密码,那有请查阅本系列的第4节(MongoDB的使用学习之(四)权限设置--用户名.密码.端口==),你说懒得设置,那就@#¥%--

MongoDB,分组,聚合

使用聚合,db.集合名.aggregate- 而不是find 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数.MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理.管道操作是可以重复的. 每一个操作符(集合)都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作符,对于最后一个操作符,是将结果返回给客户端 //分组(这里制定了分组字段 $+字段名)//这里可以理解为,吧lastModifyBy