lrx182125 发表于 2018-10-25 08:49:50

MongoDB(4): 聚合框架

  一、简介
  MongoDB的聚合框架,主要用来对集合中的文档进行变换和组合,从而对数据进行分析以加以利用。
  聚合框架的基本思路是:
  采用多个构件来创建一个管道,用于对一连串的文档进行处理。
  这些构件包括:
  筛选(filtering)、投影(projecting)、分组(grouping)、排序(sorting)、限制(limiting)和跳过(skipping)。
  使用聚合框架的方式:
  db.集合.aggregate(构件1,构件2…)
  注意:由于聚合的结果要返回到客户端,因此聚合结果必须限制在16M以内,这是MongoDB支持的最大响应消息的大小。
  二、使用例子
  2.1、准备样例数据
for(var i=0;i 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 : ]
  2:$subtract:
  3:$multiply:]
  4:$divice:
  5:$mod:
  管道操作符$project的日期表达式:
  聚合框架包含了一些用于提取日期信息的表达式,如下:
  $year、$month、$week、$dayOfMonth、$dayOfWeek、$dayOfYear、$hour、$minute、$second 。
  注意:这些只能操作日期型的字段,不能操作数据,使用示例:
  {"$project":{"opeDay":{"$dayOfMonth":"$recoredTime"}}}
  管道操作符$project的字符串表达式:
  1:$substr :
  2:$concat:]
  3:$toLower:expr
  4:$toUpper:expr
  例如:{"$project":{"sid":{$concat:["$studentId","cc"]}}}
  管道操作符$project的逻辑表达式:
  1:$cmp: :比较两个表达式,0表示相等,正数前面的大,负数后面的大
  2:$strcasecmp: :比较两个字符串,区分大小写,只对由罗马字符组成的字符串有效
  3:$eq、$ne、$gt、$gte、$lt、$lte :
  4:$and、$or、$not
  5:$cond::如果boolean表达式为true,返回true表达式,否则返回false表达式
  6:$ifNull::如果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.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来定义函数作为键,另外一定要返回对象的格式


页: [1]
查看完整版本: MongoDB(4): 聚合框架