当前处理是mapReduce
如果您需要在服务器上执行此操作并对排名靠前的结果进行排序并只保留排名前 100 的结果,那么您可以使用 mapReduce 来执行此操作,如下所示:
db.test.mapReduce(
function() {
var input = [0.1,0.3,0.4];
var value = Array.sum(this.vals.map(function(el,idx) {
return Math.abs( el - input[idx] )
}));
emit(null,{ "output": [{ "_id": this._id, "value": value }]});
},
function(key,values) {
var output = [];
values.forEach(function(value) {
value.output.forEach(function(item) {
output.push(item);
});
});
output.sort(function(a,b) {
return a.value < b.value;
});
return { "output": output.slice(0,100) };
},
{ "out": { "inline": 1 } }
)
因此,映射器函数在同一键下执行计算和输出所有内容,因此所有结果都发送到减速器。最终输出将包含在单个输出文档的数组中,因此重要的是,所有结果都使用相同的键值发出,并且每个发出的输出本身就是一个数组,以便 MapReduce 可以正常工作。
排序和缩减是在缩减程序本身中完成的,当检查每个发出的文档时,元素将被放入单个临时数组中,进行排序,然后返回顶部结果。
这很重要,这就是发射器将其生成为数组的原因,即使一开始是单个元素。 MapReduce 的工作原理是按“块”处理结果,因此即使所有发出的文档具有相同的键,它们也不会立即全部处理。相反,reducer 将其结果放回发出的结果队列中进行缩减,直到该特定键只剩下一个文档。
为了列表的简洁性,我将此处的“切片”输出限制为 10,并包含统计数据以表明观点,因为可以看到在这 10000 个样本上调用的 100 个归约周期:
{
"results" : [
{
"_id" : null,
"value" : {
"output" : [
{
"_id" : ObjectId("56558d93138303848b496cd4"),
"value" : 2.2
},
{
"_id" : ObjectId("56558d96138303848b49906e"),
"value" : 2.2
},
{
"_id" : ObjectId("56558d93138303848b496d9a"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d93138303848b496ef2"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497861"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497b58"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497ba5"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d94138303848b497c43"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d95138303848b49842b"),
"value" : 2.1
},
{
"_id" : ObjectId("56558d96138303848b498db4"),
"value" : 2.1
}
]
}
}
],
"timeMillis" : 1758,
"counts" : {
"input" : 10000,
"emit" : 10000,
"reduce" : 100,
"output" : 1
},
"ok" : 1
}
所以这是一个单一的文档输出,采用特定的mapReduce 格式,其中“值”包含一个元素,该元素是排序和有限结果的数组。
未来的处理是聚合的
截至撰写本文时,MongoDB 目前最新的稳定版本是 3.0,它缺乏使您的操作成为可能的功能。但即将发布的 3.2 版本引入了新的运算符,使这成为可能:
db.test.aggregate([
{ "$unwind": { "path": "$vals", "includeArrayIndex": "index" }},
{ "$group": {
"_id": "$_id",
"result": {
"$sum": {
"$abs": {
"$subtract": [
"$vals",
{ "$arrayElemAt": [ { "$literal": [0.1,0.3,0.4] }, "$index" ] }
]
}
}
}
}},
{ "$sort": { "result": -1 } },
{ "$limit": 100 }
])
为了简洁起见,还限制为相同的 10 个结果,您将得到如下输出:
{ "_id" : ObjectId("56558d96138303848b49906e"), "result" : 2.2 }
{ "_id" : ObjectId("56558d93138303848b496cd4"), "result" : 2.2 }
{ "_id" : ObjectId("56558d96138303848b498e31"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497c43"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497861"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499037"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b498db4"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496ef2"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496d9a"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499182"), "result" : 2.1 }
这之所以成为可能,很大程度上是由于$unwind https://docs.mongodb.org/master/reference/operator/aggregation/unwind/被修改为在结果中投影包含数组索引的字段,并且还由于$arrayElemAt https://docs.mongodb.org/master/reference/operator/aggregation/arrayElemAt/这是一个新的运算符,可以从提供的索引中提取数组元素作为奇异值。
这允许通过输入数组中的索引位置“查找”值,以便将数学应用到每个元素。输入数组由现有的$literal https://docs.mongodb.org/manual/reference/operator/aggregation/literal/运算符所以$arrayElemAt
不会抱怨并将其识别为数组(目前似乎是一个小错误,因为其他数组函数没有直接输入的问题)并通过使用生成的“index”字段获取适当的匹配索引值$unwind
进行比较。
数学计算是通过$subtract https://docs.mongodb.org/manual/reference/operator/aggregation/subtract/当然还有另一个新的运营商$abs https://docs.mongodb.org/master/reference/operator/aggregation/abs/以满足您的功能。此外,由于首先需要展开数组,所以所有这些都是在一个内部完成的$group https://docs.mongodb.org/manual/reference/operator/aggregation/group/阶段累积每个文档的所有数组成员并通过$sum https://docs.mongodb.org/manual/reference/operator/aggregation/sum/累加器。
最后所有结果文档都经过处理$sort https://docs.mongodb.org/manual/reference/operator/aggregation/sort/然后是$limit https://docs.mongodb.org/manual/reference/operator/aggregation/limit/应用于仅返回顶部结果。
Summary
即使 MongoDB 聚合框架即将推出新功能,但哪种方法实际上对结果更有效仍然存在争议。这主要是因为仍然需要$unwind
数组内容,它有效地为要处理的管道中的每个数组成员生成每个文档的副本,这通常会导致开销。
因此,虽然在新版本发布之前,mapReduce 是实现此目的的唯一方法,但它实际上可能优于聚合语句,具体取决于要处理的数据量,并且尽管聚合框架适用于本机编码运算符而不是翻译后的 JavaScript运营。
与所有事情一样,始终建议进行测试,以确定哪种情况更适合您的目的,以及哪种情况为您的预期处理提供最佳性能。
Sample
当然,问题中提供的示例文档的预期结果是0.9
通过应用数学。但仅出于我的测试目的,这里有一个简短的列表,用于生成一些示例数据,我想至少验证 mapReduce 代码是否正常工作:
var bulk = db.test.initializeUnorderedBulkOp();
var x = 10000;
while ( x-- ) {
var vals = [0,0,0];
vals = vals.map(function(val) {
return Math.round((Math.random()*10),1)/10;
});
bulk.insert({ "vals": vals });
if ( x % 1000 == 0) {
bulk.execute();
bulk = db.test.initializeUnorderedBulkOp();
}
}
这些数组是完全随机的单个小数点值,因此我作为示例输出给出的列出的结果没有太多分布。