您似乎要求退回满足最多条件的文档,可能不是所有条件。基本过程是一个$or 查询以返回可以匹配任一条件的文档。然后你基本上需要一个语句来计算文档中“多少个term”,并返回最匹配的那个。
所以这里的组合是一个.aggregate() 语句,使用来自$or 的初始结果来计算然后对结果进行排序:
// initial targets object
var userTarget = {
"cluster":"01",
"env":"DC",
"core":"PO"
};
// Convert to $or condition
// and the calcuation condition to match
var orCondition = [],
scoreCondition = []
Object.keys(userTarget).forEach(function(key) {
var query = {},
cond = { "$cond": [{ "$eq": ["$target." + key, userTarget[key]] },1,0] };
query["target." + key] = userTarget[key];
orCondition.push(query);
scoreCondition.push(cond);
});
// Run aggregation
Model.aggregate(
[
// Match with condition
{ "$match": { "$or": orCondition } },
// Calculate a "score" based on matched fields
{ "$project": {
"target": 1,
"score": {
"$add": scoreCondition
}
}},
// Sort on the greatest "score" (descending)
{ "$sort": { "score": -1 } },
// Return the first document
{ "$limit": 1 }
],
function(err,result) {
// check errors
// Remember that result is an array, even if limitted to one document
console.log(result[0]);
}
)
所以在处理聚合语句之前,我们将根据userTarget 对象中的输入生成管道操作的动态部分。这将产生一个像这样的orCondition:
{ "$match": {
"$or": [
{ "target.cluster" : "01" },
{ "target.env" : "DC" },
{ "target.core" : "PO" }
]
}}
scoreCondition 会扩展成这样的编码:
"score": {
"$add": [
{ "$cond": [{ "$eq": [ "$target.cluster", "01" ] },1,0] },
{ "$cond": [{ "$eq": [ "$target.env", "DC" ] },1,0] },
{ "$cond": [{ "$eq": [ "$target.core", "PO" ] },1,0] },
]
}
这些将用于选择可能的文档,然后用于计算可能匹配的术语。具体来说,“分数”是通过评估 $cond 三元运算符中的每个条件得出的,然后在匹配的情况下将分数分配为1,或者在该字段上没有匹配的情况下将分数分配到0。
如果需要,可以很简单地更改逻辑,为每个字段分配更高的“权重”,并根据被认为的匹配重要性为得分分配不同的值。无论如何,您只需$add 将这些得分结果汇总为每个字段的总“得分”。
那么只需将$sort 应用于返回的“分数”,然后使用$limit 即可返回顶部文档。
这不是超级有效,因为即使所有三个条件都匹配,您对数据提出的基本问题也不能假定存在,因此它需要查看 “至少一个" 条件是匹配的,然后从这些可能的结果中找出“最佳匹配”。
理想情况下,我会亲自“先”运行一个附加查询,看看是否满足所有三个条件,如果不满足,则查找其他情况。这仍然是两个单独的查询,与仅将所有字段的“和”条件作为$or 中的第一条语句推送不同。
所以我认为首选的实现应该是:
-
查找与所有给定字段值匹配的文档;如果不是那么
-
在每个字段上运行非/或并计算条件匹配。
这样,如果所有字段都匹配,那么第一个查询是最快的,如果没有实际结果,只需要回退到清单中显示的较慢但必需的实现。