MongoDB 搜索和排序,匹配数和完全匹配
MongoDB Search and Sort, with Number of Matches and Exact Match
我想创建一个小型 MongoDB 搜索查询,我想在其中根据完全匹配后跟否对结果集进行排序。的比赛。
例如。如果我有以下标签
Physics
11th-Physics
JEE-IIT-Physics
Physics-Physics
然后,如果我搜索 "Physics",它应该排序为
Physics
Physics-Physics
11th-Physics
JEE-IIT-Physics
寻找您在这里谈论的那种 "scoring" 是 "imperfect solutions" 中的一个练习。在这种情况下,此处的 "best fit" 以 "text search" 开头,而 "imperfect" 是使用 MongoDB.
的文本搜索功能时首先要考虑的术语
MongoDB 是 "not" 专用的 "text search" 产品,它也不会(像大多数数据库一样)试图成为一个产品。 "text search" 的全部功能保留给专业领域的专用产品。所以可能不是最合适的,但是 "text search" 是那些可以忍受这些限制并且不想实施另一个引擎的人的一个选择。还是!至少。
话虽如此,让我们看看您可以对给定的数据样本做什么。首先在集合中设置一些数据:
db.junk.insert([
{ "data": "Physics" },
{ "data": "11th-Physics" },
{ "data": "JEE-IIT-Physics" },
{ "data": "Physics-Physics" },
{ "data": "Something Unrelated" }
])
然后当然要 "enable" 文本搜索功能,然后您需要使用 "text" 索引类型对文档中的至少一个字段进行索引:
db.junk.createIndex({ "data": "text" })
现在是 "ready to go",让我们看一下第一个基本查询:
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
}
所以这是 "close" 您想要的结果,但是当然没有 "exact match" 组件。此外,带有 $text
运算符的文本搜索功能在此处使用的逻辑意味着 "Physics-Physics" 是此处的首选匹配项。
这是因为引擎无法识别"non words",例如中间的"hyphen"。对它来说,"Physics"这个词在文档的索引内容中出现了多次,因此它有更高的分数。
现在您的其余逻辑取决于 "exact match" 的应用以及您的意思。如果您在字符串中寻找 "Physics" 并且 "not" 被 "hyphens" 或其他字符包围,则以下内容不适合。但是你可以只匹配一个字段 "value" 即 "exactly" 只是 "Physics":
db.junk.aggregate([
{ "$match": {
"$text": { "$search": "Physics" }
}},
{ "$project": {
"data": 1,
"score": {
"$add": [
{ "$meta": "textScore" },
{ "$cond": [
{ "$eq": [ "$data", "Physics" ] },
10,
0
]}
]
}
}},
{ "$sort": { "score": -1 } }
])
这会给你一个结果,既查看引擎产生的 "textScore",然后应用一些数学与逻辑测试。在这种情况下 "data" 正好等于 "Physics" 那么我们 "weight" 使用 $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
}
这就是 aggregation framework can do for you, by allowing manipulation of the returned data with additional conditions. The end result is passed to the $sort
阶段(注意它按降序颠倒)允许新值成为排序键。
但是聚合框架真的只能像这样处理字符串上的"exact matches"。目前没有工具可以处理 return 对投影有意义的字符串中的正则表达式匹配或索引位置。甚至没有逻辑匹配。而$regex
操作只用于查询中的"filter",所以这里不用。
因此,如果您在 "phrase" 中查找比 "string equals" 完全匹配更复杂的内容,那么另一个选项是使用 mapReduce.
这是另一种 "imperfect" 方法,因为 mapReduce
命令的限制意味着引擎此类查询的 "textScore" 是 "completely gone"。虽然将正确选择实际文档,但引擎无法使用固有的 "ranking data"。这是 MongoDB "projects" 将 "score" 首先放入文档的副产品,而 "projection" 不是 mapReduce
可用的功能。
但是您可以 "play with" 使用 JavaScript 的字符串,如我的 "imperfect" 示例:
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"
}
}
我这里的"silly little algorithm"基本上就是把这里匹配的字符串的"first"和"last"索引位置都取下来,然后相加得到一个分数。这可能不是您真正想要的,但重点是,如果您可以在 JavaScript 中编写您的逻辑,那么您可以将它扔到引擎中以产生所需的 "ranking".
这里唯一真正的"trick"要记住的是"score"必须是分组[=140=的"preceeding"部分] 在这里,并且如果包含原始文档 _id
值,则复合键部分 必须 重命名,否则 _id
将优先顺序。
这只是 mapReduce
的一部分,其中作为 "optimization" 所有输出 "key" 值在被减速器处理之前在 "ascending order" 中排序。这当然在这里什么都不做,因为我们不是 "aggregating",只是一般地使用 JavaScript 转轮和 mapReduce
的文档重塑。
总的来说,这些是可用的选项。 None 其中完美,但您可能能够接受它们,甚至只是 "accept" 默认引擎结果。
如果您想要更多,请查看更适合的外部 "dedicated" 文本搜索产品。
旁注:此处的 $text
搜索优于 $regex
,因为它们可以使用索引。 "non-anchored" 正则表达式(没有插入符号 ^
)不能将索引与 MongoDB 结合使用。因此,$text
搜索通常会成为在短语中查找 "words" 的更好基础。
另一种方法是使用 $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
}
]
我想创建一个小型 MongoDB 搜索查询,我想在其中根据完全匹配后跟否对结果集进行排序。的比赛。
例如。如果我有以下标签
Physics
11th-Physics
JEE-IIT-Physics
Physics-Physics
然后,如果我搜索 "Physics",它应该排序为
Physics
Physics-Physics
11th-Physics
JEE-IIT-Physics
寻找您在这里谈论的那种 "scoring" 是 "imperfect solutions" 中的一个练习。在这种情况下,此处的 "best fit" 以 "text search" 开头,而 "imperfect" 是使用 MongoDB.
的文本搜索功能时首先要考虑的术语MongoDB 是 "not" 专用的 "text search" 产品,它也不会(像大多数数据库一样)试图成为一个产品。 "text search" 的全部功能保留给专业领域的专用产品。所以可能不是最合适的,但是 "text search" 是那些可以忍受这些限制并且不想实施另一个引擎的人的一个选择。还是!至少。
话虽如此,让我们看看您可以对给定的数据样本做什么。首先在集合中设置一些数据:
db.junk.insert([
{ "data": "Physics" },
{ "data": "11th-Physics" },
{ "data": "JEE-IIT-Physics" },
{ "data": "Physics-Physics" },
{ "data": "Something Unrelated" }
])
然后当然要 "enable" 文本搜索功能,然后您需要使用 "text" 索引类型对文档中的至少一个字段进行索引:
db.junk.createIndex({ "data": "text" })
现在是 "ready to go",让我们看一下第一个基本查询:
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
}
所以这是 "close" 您想要的结果,但是当然没有 "exact match" 组件。此外,带有 $text
运算符的文本搜索功能在此处使用的逻辑意味着 "Physics-Physics" 是此处的首选匹配项。
这是因为引擎无法识别"non words",例如中间的"hyphen"。对它来说,"Physics"这个词在文档的索引内容中出现了多次,因此它有更高的分数。
现在您的其余逻辑取决于 "exact match" 的应用以及您的意思。如果您在字符串中寻找 "Physics" 并且 "not" 被 "hyphens" 或其他字符包围,则以下内容不适合。但是你可以只匹配一个字段 "value" 即 "exactly" 只是 "Physics":
db.junk.aggregate([
{ "$match": {
"$text": { "$search": "Physics" }
}},
{ "$project": {
"data": 1,
"score": {
"$add": [
{ "$meta": "textScore" },
{ "$cond": [
{ "$eq": [ "$data", "Physics" ] },
10,
0
]}
]
}
}},
{ "$sort": { "score": -1 } }
])
这会给你一个结果,既查看引擎产生的 "textScore",然后应用一些数学与逻辑测试。在这种情况下 "data" 正好等于 "Physics" 那么我们 "weight" 使用 $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
}
这就是 aggregation framework can do for you, by allowing manipulation of the returned data with additional conditions. The end result is passed to the $sort
阶段(注意它按降序颠倒)允许新值成为排序键。
但是聚合框架真的只能像这样处理字符串上的"exact matches"。目前没有工具可以处理 return 对投影有意义的字符串中的正则表达式匹配或索引位置。甚至没有逻辑匹配。而$regex
操作只用于查询中的"filter",所以这里不用。
因此,如果您在 "phrase" 中查找比 "string equals" 完全匹配更复杂的内容,那么另一个选项是使用 mapReduce.
这是另一种 "imperfect" 方法,因为 mapReduce
命令的限制意味着引擎此类查询的 "textScore" 是 "completely gone"。虽然将正确选择实际文档,但引擎无法使用固有的 "ranking data"。这是 MongoDB "projects" 将 "score" 首先放入文档的副产品,而 "projection" 不是 mapReduce
可用的功能。
但是您可以 "play with" 使用 JavaScript 的字符串,如我的 "imperfect" 示例:
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"
}
}
我这里的"silly little algorithm"基本上就是把这里匹配的字符串的"first"和"last"索引位置都取下来,然后相加得到一个分数。这可能不是您真正想要的,但重点是,如果您可以在 JavaScript 中编写您的逻辑,那么您可以将它扔到引擎中以产生所需的 "ranking".
这里唯一真正的"trick"要记住的是"score"必须是分组[=140=的"preceeding"部分] 在这里,并且如果包含原始文档 _id
值,则复合键部分 必须 重命名,否则 _id
将优先顺序。
这只是 mapReduce
的一部分,其中作为 "optimization" 所有输出 "key" 值在被减速器处理之前在 "ascending order" 中排序。这当然在这里什么都不做,因为我们不是 "aggregating",只是一般地使用 JavaScript 转轮和 mapReduce
的文档重塑。
总的来说,这些是可用的选项。 None 其中完美,但您可能能够接受它们,甚至只是 "accept" 默认引擎结果。
如果您想要更多,请查看更适合的外部 "dedicated" 文本搜索产品。
旁注:此处的 $text
搜索优于 $regex
,因为它们可以使用索引。 "non-anchored" 正则表达式(没有插入符号 ^
)不能将索引与 MongoDB 结合使用。因此,$text
搜索通常会成为在短语中查找 "words" 的更好基础。
另一种方法是使用 $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
}
]