我有一个Item
模型,其属性为category
。我希望项目计数按类别分组。我为这个功能写了一个地图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。如何实现我想要的功能?
答案 0 :(得分:2)
在MongoDB(a few rules, actually)中编写map-reduce代码时,必须遵循一条规则。一个是emit(发出键/值对)必须具有与reduce函数返回的值相同的格式。
如果您emit(this.key, this.value)
,则reduce必须返回与this.value
完全相同的类型。如果你emit({},1)
那么reduce必须返回一个数字。如果你emit({},{category: this.category})
那么reduce必须返回格式为{category:"string"}
的文档(假设category是一个字符串)。
所以显然不能成为你想要的东西,因为你想要总数,所以让我们来看看减少的东西是什么,并从你应该发出的东西中解决。
在最后,您希望累积一个文档,其中每个类别都有一个键名,其值是一个表示其出现次数的数字。类似的东西:
{category_name1:total, category_name2:total}
如果是这种情况,那么正确的地图功能将emit({},{"this.category":1})
,在这种情况下,您的reduce需要将每个键对应一个类别的数字相加。
这是地图的样子:
map=function (){
category = { };
category[this.category]=1;
emit({},category);
}
这是正确的相应减少:
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=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。
答案 1 :(得分:0)
我同意Neil Lunn的评论。
从我提供的信息中我可以看到,如果您使用的MongoDB版本大于或等于2.2,则可以使用聚合框架而不是map-reduce。
db.items.aggregate([
{ $group: { _id: '$category', category_count: { $sum: 1 } }
])
这更简单,更高效(见Map/Reduce vs. Aggregation Framework)