关于 MongoDB 是否可以像这样对计算中的数据进行排序,答案实际上是“是”和“否”。它当然可以做到,但可能不适合您的目的。
MongoDB 用来进行任何类型计算的两个工具是 aggregation framework 和 mapReduce。前者目前缺乏真正以实际方式处理此问题的运营商。第二个可以通过将要排序的组件放在分组键中(即使没有实际的分组)来“欺骗”排序,作为 mapReduce 工作方式的工件。
所以你基本上可以用这样的东西来应用数学:
db.data.mapReduce(
function() {
var vals = this.data.map(function(el){ return el.value }),
uniq = {};
vals.forEach(function(el) {
if (!uniq.hasOwnProperty(el)) {
uniq[el] = 1;
} else {
uniq[el]++;
}
});
var weight = Array.sum(Object.keys(uniq).map(function(key) {
return uniq[key] * key
})) / Array.sum(Object.keys(uniq).map(function(key) {
return uniq[key];
}))
var id = this._id;
delete this._id;
emit({ "weight": weight, "orig": id },this);
},
function() {},
{ "out": { "inline": 1 } }
)
这会给你这个输出:
{
"results" : [
{
"_id" : {
"weight" : 2.5,
"orig" : "wPg4jzJsEFXNxR5Wf"
},
"value" : {
"caveId" : "56424a93819e7419112c883e",
"data" : [
{
"value" : 1
},
{
"value" : 3
},
{
"value" : 4
},
{
"value" : 2
}
]
}
},
{
"_id" : {
"weight" : 2.75,
"orig" : "oSrtv33MgnkJFvNan"
},
"value" : {
"caveId" : "56424a93819e7419112c949f",
"data" : [
{
"value" : 1
},
{
"value" : 4
},
{
"value" : 4
},
{
"value" : 2
}
]
}
},
{
"_id" : {
"weight" : 3.3333333333333335,
"orig" : "gJRMMQPwDwjFrL7zz"
},
"value" : {
"caveId" : "56424a93819e7419112c8727",
"data" : [
{
"value" : 5
},
{
"value" : 1
},
{
"value" : 4
}
]
}
}
]
}
所以所有结果都已排序,但当然限制适用于 mapReduce 只能产生低于 16MB BSON 限制的“内联”输出,或者将结果写入另一个集合。
即使在聚合框架中添加了可以在此处提供帮助的新功能(来自当前的开发系列 3.1.x),这仍然需要与$unwind 进行一些杂耍才能以任何方式获得元素的“总和”(还没有“reduce”功能这样的功能),这并不能使其成为稳定或实用的替代方案。
所以你可以用 mapReduce 来做,但是为了我的钱,我会有另一个过程来计算它以定期运行(或在更新时触发)并更新文档上的标准“权重”字段,然后可以直接使用用于排序。
在您的文档中设置一个值始终是最高效的选项。
如果好奇,您可以获取 MongoDB 的开发分支版本(3.1.x 系列)或之后的任何版本,然后应用如下聚合管道:
db.data.aggregate([
{ "$project": {
"caveId": 1,
"data": 1,
"conv": {
"$setUnion": [
{ "$map": {
"input": "$data",
"as": "el",
"in": "$$el.value"
}},
[]
]
},
"orig": {
"$map": {
"input": "$data",
"as": "el",
"in": "$$el.value"
}
}
}},
{ "$project": {
"caveId": 1,
"data": 1,
"conv": 1,
"orig": 1,
"counts": { "$map": {
"input": "$conv",
"as": "el",
"in": {
"$size": {
"$filter": {
"input": "$orig",
"as": "o",
"cond": {
"$eq": [ "$$o", "$$el" ]
}
}
}
}
}}
}},
{ "$unwind": { "path": "$conv", "includeArrayIndex": true } },
{ "$group": {
"_id": "$_id",
"caveId": { "$first": "$caveId" },
"data": { "$first": "$data" },
"counts": { "$first": "$counts" },
"mult": {
"$sum": {
"$multiply": [
"$conv.value",
{ "$arrayElemAt": [ "$counts", "$conv.index" ] }
]
}
}
}},
{ "$unwind": "$counts" },
{ "$group": {
"_id": "$_id",
"caveId": { "$first": "$caveId" },
"data": { "$first": "$data" },
"count": { "$sum": "$counts" },
"mult": { "$first": "$mult" }
}},
{ "$project": {
"data": 1,
"weight": { "$divide": [ "$mult", "$count" ] }
}},
{ "$sort": { "weight": 1 } }
])
但即使在 $unwind 中使用 $filter 和 "includeArrayIndex" 之类的助手,以及稍后使用该索引将不同元素与它们的计数匹配的 $arrayElemAt 运算符,$unwind 的使用以任何方式都可以做到这一点一个无效的解决方案。
如果像$map 这样的运算符可以生成配对所需的索引值,并且引入任何方法来类似地对数组结果进行“内联求和”运算或其他数学运算而不处理@,那么将来可能会变得实用987654332@。但在撰写本文时,即使在开发中,它也不存在。