我提供了产品搜索功能,
用户可以通过多个标签进行搜索,
例如,用户可以搜索"iphone,128G,usa">
如果搜索词在标题中匹配,它将得到3分,
如果搜索词在标签中匹配,它将得到1分。
如何重写当前查询以执行结果。
- 文档1将获得:7分
- 文档2将获得:4分
示例文件1
"title": "iphone 6 128G",
"tag": [
"usa",
"golden",
]
示例文件2
"title": "iphone 4 64G",
"tag": [
"usa",
"golden",
]
当前查询
collection.aggregate(
{
"$match" => {
"tag":{ "$in"=> q_params },
}
},
{ "$unwind" => "$tag" },
{ "$match" => { "tag"=> { "$in"=> q_params } } },
{ "$group" => { "_id"=> {"title":"$title"},
"points"=> { "$sum"=>1 } } },
{ "$sort" => { "points"=> -1 } }
)
我认为您处理这一问题的方式有点错误,对数据库的"模糊匹配"要求过高。相反,考虑一下这个修改后的数据样本:
db.items.insert([
{
"title": "iphone 6 128G",
"tags": [
"iphone",
"iphone6",
"128G",
"usa",
"golden",
]
},
{
"title": "iphone 4 64G",
"tags": [
"iphone",
"iphone4",
"64G",
"usa",
"golden",
]
}
])
现在,你是不是考虑这样一个"搜索短语":
"iphone4 128G usa">
然后,您需要实现自己的应用程序逻辑(这并不是一件困难的事情,只是引用主标签(,该逻辑可以扩展为以下内容:
var searchedTags = ["iphone", "iphone4", "128G", "usa"]
您可以构造这样的管道查询:
db.items.aggregate([
{ "$match": { "tags": { "$in": searchedTags } } },
{ "$project": {
"title": 1,
"tags": 1,
"score": {
"$let": {
"vars": {
"matchSize":{
"$size": {
"$setIntersection": [
"$tags",
searchedTags
]
}
}
},
"in": {
"$add": [
"$$matchSize",
{ "$cond": [
{ "$eq": [
"$$matchSize",
{ "$size": "$tags" }
]},
"$$matchSize",
0
]}
]
}
}
}
}},
{ "$sort": { "score": -1 } }
])
返回以下结果:
{
"_id" : ObjectId("55b3551164518e494632fa19"),
"title" : "iphone 6 128G",
"tags" : [
"iphone",
"iphone6",
"128G",
"usa",
"golden"
],
"score" : 3
}
{
"_id" : ObjectId("55b3551164518e494632fa1a"),
"title" : "iphone 4 64G",
"tags" : [
"iphone",
"iphone4",
"64G",
"usa",
"golden"
],
"score" : 2
}
因此,更多的"标签"比赛总是获胜。
但如果这个短语改成这样的话:
"iphone4 64G美国黄金">
这导致了像这样的解析标签:
var searchedTags = ["iphone", "iphone4", "64G", "usa", "golden"]
然后,相同的查询管道生成以下内容:
{
"_id" : ObjectId("55b3551164518e494632fa1a"),
"title" : "iphone 4 64G",
"tags" : [
"iphone",
"iphone4",
"64G",
"usa",
"golden"
],
"score" : 10
}
{
"_id" : ObjectId("55b3551164518e494632fa19"),
"title" : "iphone 6 128G",
"tags" : [
"iphone",
"iphone6",
"128G",
"usa",
"golden"
],
"score" : 3
}
在哪里,你不仅可以从一个文档上提供的标签上获得比另一个更多的匹配,而且因为其中一个文档与提供的"所有"标签匹配,所以得分会进一步提高,使其排名比只匹配相同数量标签的文档更靠前。
要分解它,首先考虑$let
表达式为管道中的元素声明了一个"变量",这样我们就不会通过在多个位置为生成的$$matchSize
值键入相同的表达式来"重复自己"。
该变量本身是通过从searchedTags
数组的$setIntersection
和$tags
数组本身计算出结果数组来确定的。"交集"的结果只是那些匹配的项,这为测试该数组的$size
提供了空间。
因此,稍后在将该匹配的$size
归因于"分数"时,通过三元$cond
来考虑$$matchSize
是否等于$tags
的原始长度。如果为真,则$$matchSize
会被添加到自身("标签"长度的两倍的分数(,因为它与所提供的标签"完全匹配",否则该条件的返回结果为0
。
用$add
处理这两个数字结果会为每个文档生成最终的总"分数"值。
主要的一点是聚合框架缺少运算符来对字符串(如标题(进行任何类型的"模糊匹配"。您可以在$match
阶段内$regex
匹配,因为这基本上是一个查询运算符,它只会"过滤"结果。
你可以"乱来",但对于正则表达式,你真正想要的是为匹配的术语获得一个数字"分数"。这种拆分(尽管在其他语言的regex运算符中是可能的(实际上并不可用,因此简单地对输入的"标记"进行"标记化"并将其与文档"标记"匹配更有意义。
对于"数据库"(MongoDB主要是这样(来说,这是一个更好的解决方案。或者,您甚至可以将其与$text
搜索运算符相结合,将其自己的"分数"值与此处演示的"解析标签"逻辑相结合,投射到标题上。这使得"精确匹配"更加有效。
它可以与聚合管道一起使用,但即使在它本身也不会提供坏结果:
db.items.createIndex({ "title": "text" })
db.items.find({
"$text": { "$search": "iphone 4 64G" } },
{ "score": { "$meta": "textScore" }}
).sort({ "score": { "$meta": "textScore" } })
将产生:
{
"_id" : ObjectId("55b3551164518e494632fa1a"),
"title" : "iphone 4 64G",
"tags" : [
"iphone",
"iphone4",
"64G",
"usa",
"golden"
],
"score" : 2
}
{
"_id" : ObjectId("55b3551164518e494632fa19"),
"title" : "iphone 6 128G",
"tags" : [
"iphone",
"iphone6",
"128G",
"usa",
"golden"
],
"score" : 0.6666666666666666
}
但是,如果你只想发送字符串,不想被"标记化"逻辑所困扰,并且希望其他逻辑来为你的"分数"打分,那么就看看专用的文本搜索引擎吧,它比MongoDB这样的主要功能数据库的"文本搜索"甚至基本搜索能力都要好得多。