【问题标题】:Mongoid MapReduce giving irregular results for recursive reduce functionMongoid MapReduce 为递归减少函数提供不规则结果
【发布时间】:2014-04-01 14:21:55
【问题描述】:

我有一个Item 模型,它有一个属性category。我想要按类别分组的项目计数。我为这个功能写了一个 map reduce。它工作正常。我最近编写了一个脚本来创建 5000 个项目。现在我意识到我的 map reduce 只给出了最后 80 条记录的结果。以下是mapreduce函数的代码。

map = %Q{
  function(){
    emit({},{category: this.category});
  }
}

reduce = %Q{
  function(key, values){
    var category_count = {};
    values.forEach(function(value){
      if(category_count.hasOwnProperty(value.category))
        category_count[value.category]++;  
      else
        category_count[value.category] = 1 
    })
    return category_count;
  }
}

Item.map_reduce(map,reduce).out(inline: true).first.try(:[],"value")

经过一番研究,我发现了mongodb invokes reduce function multiple times。如何才能实现我想要的功能?

【问题讨论】:

  • 也许你的脑子里没有得到足够的重视,但是这个问题在输入的文档和想要的结果方面也缺乏足够的细节来得到回应。没有人回答,因为没有足够的细节来回答。您现在没有累积接近投票的唯一原因是因为您提供了赏金。使用详细信息编辑您的问题以获得答案。
  • 有足够的细节来回答 - 我在回答中说明了他做错了什么,如何正确做以及有什么更好的方法。

标签: ruby-on-rails mongodb mapreduce mongoid


【解决方案1】:

在 MongoDB 中编写 map-reduce 代码时必须遵循一条规则 (a few rules, actually)。一是emit(发出键/值对)必须与reduce函数返回的值具有相同的格式。

如果您是emit(this.key, this.value),那么reduce 必须返回与this.value 完全相同的类型。如果你 emit({},1) 然后 reduce 必须返回一个数字。如果你emit({},{category: this.category}) 那么reduce 必须返回格式为{category:"string"} 的文档(假设category 是一个字符串)。

所以这显然不是你想要的,因为你想要总计,所以让我们看看 reduce 返回什么,并从中计算出你应该发出什么。

看起来最后你想积累一个文档,其中每个类别都有一个键名,它的值是一个代表其出现次数的数字。类似的东西:

{category_name1:total, category_name2:total}

如果是这种情况,那么正确的 map 函数应该是 emit({},{"this.category":1}),在这种情况下,reduce 需要将对应于类别的每个键的数字相加。

地图应该是这样的:

map=function (){
     category = { };
     category[this.category]=1;
     emit({},category);
}

这里是正确的对应reduce:

reduce=function (key,values) {
     var category_count = {};
     values.forEach(function(value){
        for (cat in value) {
           if( !category_count.hasOwnProperty(cat) ) category_count[cat]=0;
           category_count[cat] += value[cat];
        }
     });
     return category_count;
}

请注意,它满足two other requirements for MapReduce - 如果reduce 函数 被调用(如果您的集合中只有一个文档,则会出现这种情况),它会正常工作,并且如果满足以下条件,它将正常工作reduce 函数被多次调用(当您拥有超过 100 个文档时会发生这种情况)。

一种更传统的方法是将类别名称作为键发出,将数字作为值发出。这简化了 map 和 reduce:

map=function() { 
   emit(this.category, 1);
}

reduce=function(key,values) {
    var count=0;
    values.forEach(function(val) {
        count+=val;
    }
    return count;
}

这将汇总每个类别出现的次数。这满足 MapReduce 的要求 - 如果从不调用 reduce 函数(对于只出现一次的任何类别都是这种情况),它可以正常工作,并且如果 reduce 函数被多次调用,它将正常工作次(如果任何类别出现超过 100 次就会发生这种情况)。

正如其他人指出的那样,聚合框架通过以下方式使相同的练习变得更加简单:

db.collection.aggregate({$group:{_id:"$category",count:{$sum:1}}})

尽管这与我展示的第二个 mapReduce 的格式相匹配,而不是您所拥有的将类别名称作为键输出的原始格式。不过aggregation framework will always be significantly faster than MapReduce.

【讨论】:

    【解决方案2】:

    我同意尼尔伦的评论。

    从提供的信息中我可以看出,如果您使用的 MongoDB 版本大于或等于 2.2,则可以使用聚合框架而不是 map-reduce。

    db.items.aggregate([
      { $group: { _id: '$category', category_count: { $sum: 1 } }
    ])
    

    更简单、更高效(参见Map/Reduce vs. Aggregation Framework

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多