MongoDB中使用MapReduce来进行聚合操作

在mongoDB的MapReduce操作中,map函数产生一些列中间数据,这些中间数据是key/value的集合。reduce函数收集具有相同中间key值的value值,合并这些value值,形成一个较小的value值的集合。
一个MongDB的MapReduce执行的过程如下所示。
这里写图片描述
在这个MapReduce操作中,首先通过query筛选出了一部分的数据,然后对着一部分的数据进行map操作,输出了一些列中间值,这些中间值的key是cust_id,value是amount。最后对中间数据做reduce操作,产生了最终的结果。最终的结果是统计出了每一个cust_id对应的value值的总和。
在mongoDB中MapReduce有两种写法,一种是使用db.runCommand来执行MapReduce,一种是db.集合名.mapReduce()。两种方法效果一样。我们从第一种说起。
使用runCommand方式来执行一个MapReduce函数通常的模板如下所示:

db.runCommand(
               {
                 mapReduce: <collection>,
                 map: <function>,
                 reduce: <function>,
                 finalize: <function>,
                 out: <output>,
                 query: <document>,
                 sort: <document>,
                 limit: <number>,
                 scope: <document>,
                 jsMode: <boolean>,
                 verbose: <boolean>,
                 bypassDocumentValidation: <boolean>,
               }
             )

对上面的每一个选项做一个说明:

字段类型描述
mapReducecollection执行MapReduce操作的集合名,这个集合在执行map操作前,会先通过query来过滤集合中的数据
mapfunction一个JavaScript函数,将输入数据输出为一些列key/value的中间数据
reducefunction一个JavaScript函数,收集具有相同中间key值的value值,合并这些value值,形成一个较小的value值的集合。
outstring或者document指定将结果输出到哪里。
querydocument可选的。在map函数操作之前指定过滤条件。
sortdocument可选的。对输入的数据document数据排序。这个选项早做优化时很有用,比如将sort的key和emit的key设定为想相同,这样在reduce函数中就可以做更少的操作,极大提升效率。sort中的key必须有索引。
limitnumber可选的。指定输入的document最大有多少条数据进入map函数。
finalizefunction可选的。在reduce函数后执行,改变输出数据。
scopedocument可选的。指定全局变量。这些变量可以在map,reduce和finalize函数中使用。
jsModeboolean可选的。指定是否在map函数和reduce函数之间将中间结果转换成BSON格式的数据。如果是false,MongoDB将会将map函数输出的JavaScript对象转换成BSON对象。这些BSON对象在调用reduce函数是,会再被转换为JavaScript对象。map-reduce操作将中间的BSON对象临时的存放在磁盘中。这将允许map-reduce处理更大量的数据。如果为true,MongoDB将不会把map函数输出的JavaScript对象转换成BSON对象。这样能提高计算速度。在key少于500,000 时可以考虑将jsMode设置为true.jsMode默认是false.
verboseBoolean可选的。指定最终的输出信息中是否包含时间信息。默认是true,包含时间信息。
bypassDocumentValidationboolean可选的。是否忽略文档检测。如果设置为true,将会允许你在插入document的时候进行必要的验证。

map Function

map函数将输入数据映射为一系列的key-value键。
map函数通常的样子就像下面这样:

function() {
   ...//一些列处理
   emit(key, value);
}

以下是map函数中的一些使用注意事项:
在map函数中,如果要引用当前文档自身,可以使用this。
在任何情况下map函数不应该访问数据库。
map函数不应该对其他函数有副作用。
在一个map函数中可以任意多次调用emit函数来输出具有key/value形式的中间数据。
现在我们有一个集合goods。数据如下:

{ "_id" : ObjectId("5a02451e8f215943b6d22a78"), "gnum" : "001", "shelfnum" : "e1", "price" : "10" }
{ "_id" : ObjectId("5a02451e8f215943b6d22a79"), "gnum" : "002", "shelfnum" : "e2", "price" : "20.3"}
{ "_id" : ObjectId("5a0245538f215943b6d22a7a"), "gnum" : "003", "shelfnum" : "e1", "price" : "7.5" }
{ "_id" : ObjectId("5a02456d8f215943b6d22a7b"), "gnum" : "004", "shelfnum" : "e2", "price" : "8.8" }
{ "_id" : ObjectId("5a02457f8f215943b6d22a7c"), "gnum" : "005", "shelfnum" : "e3", "price" : "6.6" }

有如下map函数:

var map=function() {
    emit(this.shelfnum, this.price);
}

emit函数将文档中的shelfnum作为key,gnum作为value生成了一些列键值对。经过map函数,产生了下面的中间数据:

{"e1":["10","7.5"]}
{"e2":["20.3","8.8"]}
{"e3":"6.6"}

reduce Fuction

reduce函数大概看起来就想下面的样子:

function(key, values) {
   ...//一系列处理过程
   return result;
}

reduce函数的注意点:
reduce函数不应该连接数据库。
reduce函数不应该影响外部系统的运行。
如果一个key只有一个value,那么MongoDB不回调用reduce函数。
对于同一个key,reduce函数可能会执行多次。
继续上面的例子

var reduce=function(key,values){
   totalsum=0;
   for(i=0;i<values.length;i++){
    totalsum+=parseFloat(values[i]);
   }
   return totalsum;
}

此时当执行MapReduce命令时,就会求出来每一个shelfnum对应商品价格的总和。

 db.runCommand({
            mapReduce:"goods",//指定对哪个集合做操作
            map:map,           //指定map函数
            reduce:reduce,     //指定reduce函数
            out:"goodsResult"   //指定输出结果到goodResult集合中
            })

返回结果如下:

{
        "result" : "goodsResult",
        "timeMillis" : 280,
        "counts" : {
                "input" : 5,
                "emit" : 5,
                "reduce" : 2,
                "output" : 3
        },
        "ok" : 1
}

然后查询goodsResult集合就可以拿到我们计算出来的结果了。

 db.goodsResult.find()
{ "_id" : "e1", "value" : 17.5 }
{ "_id" : "e2", "value" : 29.1 }
{ "_id" : "e3", "value" : "6.6" }

在MongoDB中, db.collection.mapReduce() 是db.runCommand的一个替代方案,可以直接对某个结合执行MapReduce操作。
orders集合中有如下结构的数据:

{
     _id: ObjectId("50a8240b927d5d8b5891743c"),
     cust_id: "abc123",
     ord_date: new Date("Oct 04, 2012"),
     status: 'A',
     price: 25,
     items: [ { sku: "mmm", qty: 5, price: 2.5 },
              { sku: "nnn", qty: 5, price: 2.5 } ]
}

我们要计算每一个顾客(cust_id)总的消费金额(price)。
定义map函数:

var mapFunction1 = function() {
                       emit(this.cust_id, this.price);
                   };

定义reduce函数:

var reduceFunction1 = function(keyCustId, valuesPrices) {
                          return Array.sum(valuesPrices);
                      };

执行MapReduce操作:

db.orders.mapReduce(
                     mapFunction1,
                     reduceFunction1,
                     { out: "map_reduce_example" }
                   )

这样就计算出了每一个顾客总的消费金额。
另外一个示例。

var mapFunction2 = function() {
                       for (var idx = 0; idx < this.items.length; idx++) {
                           var key = this.items[idx].sku;
                           var value = {
                                         count: 1,
                                         qty: this.items[idx].qty
                                       };
                           emit(key, value);
                       }
                    };
var reduceFunction2 = function(keySKU, countObjVals) {
                     reducedVal = { count: 0, qty: 0 };

                     for (var idx = 0; idx < countObjVals.length; idx++) {
                         reducedVal.count += countObjVals[idx].count;
                         reducedVal.qty += countObjVals[idx].qty;
                     }

                     return reducedVal;
                  };
var finalizeFunction2 = function (key, reducedVal) {

                       reducedVal.avg = reducedVal.qty/reducedVal.count;

                       return reducedVal;

                    };
db.orders.mapReduce( mapFunction2,
                     reduceFunction2,
                     {
                       out: { merge: "map_reduce_example" },
                       query: { ord_date:
                                  { $gt: new Date('01/01/2012') }
                              },
                       finalize: finalizeFunction2
                     }
                   )

上面这个例子中,map函数产生了类似{"mmm":[{count:1,qty: 5},...]}这样的中间数据。
reduce函数将每一个键值对中的键做了操作,计算出了count的总数和qty的总数。
finalize函数用reduce函数计算出的qty的总数除以count的总数,这样就计算出了每一个items的平均qty。当然这一步也可以直接在reduce函数里面做,省略finalize函数。

关于out的一些讨论

我们经常会遇到这样的问题,对一个集合做了MapReduce运算之后,这个集合进行了更新,此时我只想对新增的数据做MapReduce操作,而不想对整个集合再做一次MapReduce操作。并且操作后的结果需要加入到原来的集合中。这个时候我们可以通过query语句来过滤掉已经做过运算的数据,比如最常用的通过时间来过滤。在输出数据到集合时,通过out来指定是覆盖原来的集合还是将新结果合并到原来的结果。
我们上面的写法out: <collectionName>是直接生成一个新的集合,并且将结果输出到这个集合中。
完整的out看起来像下面这样:

out: { <action>: <collectionName>
        [, db: <dbName>]
        [, sharded: <boolean> ]
        [, nonAtomic: <boolean> ] }

其中action可以指定一下几个值:

 • replace
 替换原来的集合。
 • merge
 跟已经存在的集合进行合并,如果新旧集合有相同的key,用新的数据覆盖旧的数据。
 • reduce
 跟已经存在的集合进行合并,如果新旧集合有相同的key,对新旧文档执行reduce操作产生新的结果。

db:可选,数据库的名称。默认和输入集合使用同一个数据库。
sharded:boolean。可选。数据库有分片时使用。
nonAtomic:boolean。可选。只有在merge或者reduce时有效。默认是false。map-reduce操作的后续步骤会对数据库加锁。其他客户端无法访问到out集合中的数据。如果指定为true,在map-reduce操作的后续过程中,其他客户端就可以访问到out集合中的数据。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MongoDB聚合函数mapreduce是一种用于处理大规模数据的方法,它可以将数据集合分成多个小块进行处理,并将结果合并起来生成最终的输出。mapreduce包含两个部分:map和reduce。 map函数是对数据集合进行初步处理的函数,它接收一个文档作为输入,并对文档的数据进行处理,生成一个键值对作为输出。reduce函数则是对map函数生成的键值对进行处理的函数,它接收一个键和一组相关的值作为输入,并对这些值进行处理,生成一个新的键值对作为输出。 在MongoDBmapreduce函数可以通过db.collection.mapReduce()方法进行调用。该方法接收四个参数:map函数、reduce函数、输出集合的名称和一个可选的参数对象。其,参数对象可以包含过滤条件、排序规则、限制条件等。 下面是一个简单的示例,假设我们有一个名为orders的集合,其包含以下文档: ``` { "_id" : ObjectId("5f274c4d6d68650a3015a1c1"), "customer" : "Alice", "total" : 10 } { "_id" : ObjectId("5f274c4d6d68650a3015a1c2"), "customer" : "Bob", "total" : 20 } { "_id" : ObjectId("5f274c4d6d68650a3015a1c3"), "customer" : "Charlie", "total" : 30 } ``` 我们可以通过以下代码使用mapreduce函数对数据进行处理: ``` db.orders.mapReduce( function() { emit(this.customer, this.total); }, function(key, values) { return Array.sum(values); }, { out: "order_totals" } ) ``` 这个代码会根据每个文档的customer字段将数据分组,并将每个文档的total字段作为值。map函数生成的键值对会传递给reduce函数进行处理,最终将生成一个名为order_totals的输出集合,其包含以下文档: ``` { "_id" : "Alice", "value" : 10 } { "_id" : "Bob", "value" : 20 } { "_id" : "Charlie", "value" : 30 } ``` 这个例子只是mapreduce函数的一个简单示例,实际上它的应用非常广泛,可以处理大规模数据、生成复杂的报表等。但是,在使用mapreduce函数时需要注意一些性能问题,例如map函数的行时间、reduce函数的复杂度等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值