MongoDB搜索和排序,具有匹配数和精确匹配



我想创建一个小型MongoDB搜索查询,在其中我想对基于结果集的精确匹配进行排序,然后再对匹配数进行排序。

例如,如果我有以下标签

Physics
11th-Physics
JEE-IIT-Physics
Physics-Physics

然后,如果我搜索"物理学",它应该被归类为

Physics
Physics-Physics
11th-Physics
JEE-IIT-Physics

寻找你在这里谈论的那种"得分"是"不完美解决方案"的练习。在这种情况下,这里的"最佳匹配"从"文本搜索"开始,而"不完美"是在使用MongoDB的文本搜索功能时首先要考虑的术语。

MongoDB"不是"一个专门的"文本搜索"产品,也不是(像大多数数据库一样)试图成为一个。"文本搜索"的全部能力保留给专业领域的专用产品。因此,也许不是最合适的,但"文本搜索"是为那些能够忍受限制而不想实现另一个引擎的人提供的一种选择。还是这样!至少

话虽如此,让我们看看可以对给定的数据样本做些什么。首先在集合中设置一些数据:

db.junk.insert([
{ "data": "Physics" },
{ "data": "11th-Physics" },
{ "data": "JEE-IIT-Physics" },
{ "data": "Physics-Physics" },
{ "data": "Something Unrelated" }
])

当然,为了"启用"文本搜索功能,您需要使用"文本"索引类型对文档中的至少一个字段进行索引:

db.junk.createIndex({ "data": "text" })

现在已经"准备就绪"了,让我们来看看第一个基本查询:

db.junk.find(
{ "$text": { "$search": ""Physics"" } },
{ "score": { "$meta": "textScore" } }
).sort({ "score": { "$meta": "textScore" } })

这将给出这样的结果:

{
"_id" : ObjectId("55af83b964876554be823f33"),
"data" : "Physics-Physics",
"score" : 1.5
}
{
"_id" : ObjectId("55af83b964876554be823f30"),
"data" : "Physics",
"score" : 1
}
{
"_id" : ObjectId("55af83b964876554be823f31"),
"data" : "11th-Physics",
"score" : 0.75
}
{
"_id" : ObjectId("55af83b964876554be823f32"),
"data" : "JEE-IIT-Physics",
"score" : 0.6666666666666666
}

因此,这与您想要的结果"接近",但当然没有"完全匹配"的成分。此外,$text运算符的文本搜索功能在此处使用的逻辑意味着"物理-物理"是此处的首选匹配项。

这是因为引擎无法识别"非单词",例如中间的"连字符"。对它来说,"物理"一词在文档的索引内容中出现了好几次,因此它的得分更高。

现在,这里剩下的逻辑取决于"精确匹配"的应用以及你的意思。如果你在字符串中寻找"Physics",而"not"被"连字符"或其他字符包围,那么下面的不适合。但你可以只匹配一个字段"值",它"完全"只是"物理":

db.junk.aggregate([
{ "$match": { 
"$text": { "$search": "Physics" } 
}},
{ "$project": {
"data": 1,
"score": {
"$add": [
{ "$meta": "textScore" },
{ "$cond": [
{ "$eq": [ "$data", "Physics" ] },
10,
0
]}
]
}
}},
{ "$sort": { "score": -1 } }
])

这将给你一个结果,既可以查看引擎产生的"textScore",又可以通过逻辑测试应用一些数学。在这种情况下,如果"数据"与"物理"完全相等,则我们使用$add将分数"加权"一个附加因子:

{
"_id": ObjectId("55af83b964876554be823f30"),
"data" : "Physics",
"score" : 11
}
{
"_id" : ObjectId("55af83b964876554be823f33"),
"data" : "Physics-Physics",
"score" : 1.5
}
{
"_id" : ObjectId("55af83b964876554be823f31"),
"data" : "11th-Physics",
"score" : 0.75
}
{
"_id" : ObjectId("55af83b964876554be823f32"),
"data" : "JEE-IIT-Physics",
"score" : 0.6666666666666666
}

这就是聚合框架可以为您做的,通过允许在附加条件下操作返回的数据。最终结果被传递到$sort阶段(请注意,它按降序反转),以允许将新值作为排序键。

但是聚合框架实际上只能处理字符串上的"精确匹配"。目前还没有处理正则表达式匹配或字符串中返回有意义的投影值的索引位置的功能。甚至不符合逻辑。$regex操作仅用于查询中的"筛选",因此在这里没有用处。

因此,如果你在"短语"中寻找比"字符串等于"精确匹配更容易调用的东西,那么另一个选项是使用mapReduce。

这是另一种"不完美"的方法,因为mapReduce命令的限制意味着引擎从这样的查询中得到的"textScore"已经"完全消失"。虽然实际文档将被正确选择,但引擎无法获得固有的"排名数据"。这是MongoDB最初如何将"分数"投影"到文档中的副产品,而"投影"不是mapReduce可用的功能。

但是您可以使用JavaScript"玩"字符串,就像我的"不完美"示例:

db.junk.mapReduce(
function() {
var _id = this._id,
score = 0;
delete this._id;
score += this.data.indexOf(search);
score += this.data.lastIndexOf(search);
emit({ "score": score, "id": _id }, this);
},
function() {},
{ 
"out": { "inline": 1 },
"query": { "$text": { "$search": "Physics" } },
"scope": { "search": "Physics" }
}
)

结果如下:

{
"_id" : {
"score" : 0,
"id" : ObjectId("55af83b964876554be823f30")
},
"value" : {
"data" : "Physics"
}
},
{
"_id" : {
"score" : 8,
"id" : ObjectId("55af83b964876554be823f33")
},
"value" : {
"data" : "Physics-Physics"
}
},
{
"_id" : {
"score" : 10,
"id" : ObjectId("55af83b964876554be823f31")
},
"value" : {
"data" : "11th-Physics"
}
},
{
"_id" : {
"score" : 16,
"id" : ObjectId("55af83b964876554be823f32")
},
"value" : {
"data" : "JEE-IIT-Physics"
}
}

我自己的"愚蠢的小算法"基本上是取匹配字符串的"第一个"one_answers"最后一个"索引位置,并将它们相加以产生分数。这可能不是你真正想要的,但关键是,如果你能用JavaScript编码你的逻辑,那么你就可以把它扔到引擎上,产生想要的"排名"。

这里唯一需要记住的真正"技巧"是,"分数">必须是此处分组"密钥"的"前一个"部分,如果包含原始文档_id值,则必须重命名复合密钥部分,否则_id将优先于顺序。

这只是mapReduce的一部分,作为"优化",所有输出"键"值在由reducer处理之前都按"升序"排序。当然,这在这里没有任何作用,因为我们不是在"聚合",而只是使用JavaScript运行程序和mapReduce的文档整形。


所以总的来说,这些都是可用的选项。它们都不是完美的,但你可能可以接受它们,甚至只是"接受"默认的引擎结果。

如果你想要更多,那么看看外部的"专用"文本搜索产品,它会更适合。


旁注:此处的$text搜索优于$regex搜索,因为它们可以使用索引。"非锚定"正则表达式(没有插入符号^)不能在MongoDB中最佳地使用索引。因此,$text搜索通常是在短语中查找"单词"的更好基础。

还有一种方法是使用$indexOfCp聚合运算符来获取匹配字符串的索引,然后对索引字段应用排序

数据插入

db.junk.insert([
{ "data": "Physics" },
{ "data": "11th-Physics" },
{ "data": "JEE-IIT-Physics" },
{ "data": "Physics-Physics" },
{ "data": "Something Unrelated" }
])

查询

const data = "Physics";
db.junk.aggregate([
{ "$match": { "data": { "$regex": data, "$options": "i" }}},
{ "$addFields": { "score": { "$indexOfCP": [{ "$toLower": "$data" }, { "$toLower": data }]}}},
{ "$sort": { "score": 1 }}
])

在这里您可以测试output

[
{
"_id": ObjectId("5a934e000102030405000000"),
"data": "Physics",
"score": 0
},
{
"_id": ObjectId("5a934e000102030405000003"),
"data": "Physics-Physics",
"score": 0
},
{
"_id": ObjectId("5a934e000102030405000001"),
"data": "11th-Physics",
"score": 5
},
{
"_id": ObjectId("5a934e000102030405000002"),
"data": "JEE-IIT-Physics",
"score": 8
}
]

相关内容

  • 没有找到相关文章

最新更新