我想计算数组的所有最后元素。该数组包含整数,可能为空。
以下Map Reduce示例在大型集合(> 1000万条目)上崩溃并出现重复键错误:
var map = function() {
if(this.path.length > 0) {
emit(this.path.slice(-1)[0], 1);
}
};
var reduce = function(id, values) {
var sum = 0;
values.forEach(function(value) {
sum += value;
});
return sum;
};
db.input.mapReduce(map, reduce, {out: 'output'})
使用WT引擎的Mongo版本为3.2。该示例在较小的集合(例如~500k条目)
上运行良好完整错误:
2016-02-25T19:20:09.078+0100 E QUERY [thread1] Error: map reduce failed:{
"ok" : 0,
"errmsg" : "E11000 duplicate key error collection: my_db.tmp.mr.input_10 index: _id_ dup key: { : 174.0 }",
"code" : 11000
} :
_getErrorWithCode@src/mongo/shell/utils.js:23:13
DBCollection.prototype.mapReduce@src/mongo/shell/collection.js:1300:1
@(shell):1:1
答案 0 :(得分:1)
mapReduce示例失败的原因是因为数组的最后一个元素可以是整数,也可以是数组本身。我不确定错误消息试图告诉我的是什么。
我找到了理由感谢Blakes Seven,他提出了聚合管道。它不仅速度更快,代码更少,而且还会出现明显易懂的错误:
assert: command failed: {
"ok" : 0,
"errmsg" : "insert for $out failed: { connectionId: 599, err: \"can't use an array for _id\", code: 2, n: 0, ok: 1.0 }",
"code" : 16996
} : aggregate failed
答案 1 :(得分:0)
由于您使用的是MongoDB 3.2,因此mapReduce完全是错误的工具,您应该使用.aggregate()
。
MongoDB 3.2生成$slice
到聚合框架,它与.slice()
基本相同,而且还更好$arrayElemAt
,后者可以只返回最后一个元素数组作为单个值,就像你想要的那样:
db.input.aggregate([
{ "$match": { "path.0": { "$exists": true } } },
{ "$group": {
"_id": { "$arrayElemAt": [ "$path", -1 ] },
"count": { "$sum": 1 }
}},
{ "$out": "output" }
])
这就是对数组的最后一个元素进行分组($group
),并按照你想要的方式总结这些元素的每个值的计数。
此外,使用$match
初始查询来过滤掉空数组(测试是否存在0索引位置意味着它必须有一些定义的长度)比强力代码测试你效率更高正在使用。可以使mapReduce操作受益于相同的“查询”输入以进行过滤。
然后有$out
,这是.aggregate()
方法的可选项,而不像mapReduce可以返回更大结果集的“光标”。所以你甚至可能不需要它,除非你真的希望输出进入另一个集合。
这里的主要教训是“使用聚合语句”。与mapReduce不同,运算符都使用本机代码而不是解释的JavaScript。结果是它比相应的mapReduce运行得更快,并且更多。
仅仅为了记录,你的mapReduce因此更有效率(但不如聚合),这样写
db.input.mapReduce(
function() {
emit(this.path.slice(-1)[0],1);
},
function(key,values) {
return Array.sum(values);
},
{
"out": "output",
"query": { "path.0": { "$exists": true } }
}
)
此外,除非您明确设置"out"
或"merge"
选项,否则mapReduce 中的"reduce"
始终会覆盖该集合。