mongoDB - MapReduce finalize创建NaN值

时间:2016-02-03 09:51:12

标签: mongodb mapreduce mongodb-query aggregation-framework

我为以下数据结构创建了MapReduce作业:

{ "_id" : 1), "docid" : 119428, "term" : 5068, "score" : 0.198 }
{ "_id" : 2), "docid" : 154690, "term" : 5068, "score" : 0.21 }
{ "_id" : 3), "docid" : 156278, "term" : 5068, "score" : 0.128 }

{ "_id" : 4), "docid" : 700, "term" : "fire", "score" : 0.058 }
{ "_id" : 5), "docid" : 857, "term" : "fire", "score" : 0.133 }
{ "_id" : 6), "docid" : 900, "term" : "fire", "score" : 0.191 }
{ "_id" : 7), "docid" : 902, "term" : "fire", "score" : 0.047 }

我希望按照术语进行分组,然后计算平均分数。

这是我的MapReduce函数:

db.keywords.mapReduce( 
  function(){ 
      emit( this.term, this.score ); 
  }, 
  function(key, values) { 
      rv = { cnt : 0, scoresum : 0}; 
      rv.cnt = values.length; rv.scoresum = Array.sum(values); 
      return rv; 
  },  
  { 
     out: "mr_test" , 
     finalize: function(key, reduceVal) { 
        reduceVal.avg = reduceVal.scoresum / reduceVal.cnt; 
        return reduceVal;  
     } 
   } 
)

某些计算值是正确的:

{ "_id" : 5068, "value" : { "cnt" : 5, "scoresum" : 0.887, "avg" : 0.1774 } }

但是其他人正在创造一些奇怪的结构:

    { "_id" : "fire", "value" : { "cnt" : 333, "scoresum" : "[object 
BSON][object BSON]0.176[object BSON]0.1010.181[object BSON][object .....BSON]
[object BSON][object BSON]0.1910.1710.2010.363[object BSON][object BSON]", "avg" : NaN } }

我的MapReduce功能出了什么问题?

1 个答案:

答案 0 :(得分:5)

您错过了从documentation处理mapReduce操作的基本规则:

  

MongoDB可以为同一个密钥多次调用reduce函数。在这种情况下,该键的reduce函数的先前输出将成为该键的下一个reduce函数调用的输入值之一。

这意味着“两个”映射器和缩减器函数必须发出完全相同的结构并考虑该输入结构。问题当然是如果你在reduce函数中输出一个不同的结构,那么下次它返回到reduce时,输入的结构就不是预期的了。

这就是mapReduce如何处理同一个键的大数据,通过逐步减少,一遍又一遍,直到给定键只有一个结果:

db.keywords.mapReduce( 
  function(){ 
      emit( this.term, { "cnt": 1, "score": this.score } );
  }, 
  function(key, values) { 
      rv = { "cnt" : 0, "score" : 0 }; 
      values.forEach(function(value) {
          rv.cnt += value.cnt;
          rv.score += value.score;
      });
      return rv;
  },  
  { 
      "out": "mr_test" , 
      "finalize": function(key, reduceVal) { 
          reduceVal.avg = reduceVal.score / reduceVal.cnt; 
          return reduceVal;  
      } 
   }
)

但实际上使用.aggregate()方法可以更有效地完成整个事情:

db.keywords.aggregate([
    { "$group": {
        "_id": "$term",
        "cnt": { "$sum": 1 },
        "score": { "$sum": "$score" },
        "avg": { "$avg": "$score" }
    }},
    { "$out": "aggtest" }
])

甚至还有$avg聚合累加器,它可以在一次传递中为你提供平均值。

与mapReduce不同,此处使用的运算符在本机代码中执行,而不是解释JavaScript。结果更快,并且通过数据的次数更少。

实际上$group上只有一个传递,$out只是集合的可选输出,而不是返回默认的游标。而游标是mapReduce的另一个优势。