use*_*188 4 mongodb mongodb-query aggregation-framework
我提供了产品的搜索功能,
用户可以通过多个标签进行搜索
例如,用户可以搜索"iphone,128G,usa"
如果搜索字词在标题中匹配,则会得到3分,
如果搜索字词在标记中匹配,则会得1分.
我怎么能重写我当前的查询来执行结果.
"title": "iphone 6 128G",
"tag": [
"usa",
"golden",
]
Run Code Online (Sandbox Code Playgroud)
"title": "iphone 4 64G",
"tag": [
"usa",
"golden",
]
Run Code Online (Sandbox Code Playgroud)
collection.aggregate(
{
"$match" => {
"tag":{ "$in"=> q_params },
}
},
{ "$unwind" => "$tag" },
{ "$match" => { "tag"=> { "$in"=> q_params } } },
{ "$group" => { "_id"=> {"title":"$title"},
"points"=> { "$sum"=>1 } } },
{ "$sort" => { "points"=> -1 } }
)
Run Code Online (Sandbox Code Playgroud)
Bla*_*ven 11
我认为你正在以错误的方式接近这一点并且从数据库中过多地询问"模糊匹配".相反,请考虑此修订数据样本:
db.items.insert([
{
"title": "iphone 6 128G",
"tags": [
"iphone",
"iphone6",
"128G",
"usa",
"golden",
]
},
{
"title": "iphone 4 64G",
"tags": [
"iphone",
"iphone4",
"64G",
"usa",
"golden",
]
}
])
Run Code Online (Sandbox Code Playgroud)
那么你现在考虑像这样的"搜索短语":
"iphone4 128G usa"
然后你需要实现自己的应用程序逻辑(不是真的很难,只是引用主标记),扩展成这样的东西:
var searchedTags = ["iphone", "iphone4", "128G", "usa"]
Run Code Online (Sandbox Code Playgroud)
你可以构建一个像这样的管道查询:
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 } }
])
Run Code Online (Sandbox Code Playgroud)
返回以下结果:
{
"_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
}
Run Code Online (Sandbox Code Playgroud)
所以"标签"匹配越多,所有的时间都会赢.
但是如果这个短语改成了这样的话:
"iphone4 64G美国金色"
这导致解析标签如下:
var searchedTags = ["iphone", "iphone4", "64G", "usa", "golden"]
Run Code Online (Sandbox Code Playgroud)
然后相同的查询管道产生这个:
{
"_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
}
Run Code Online (Sandbox Code Playgroud)
你不仅可以在一个文档上获得比其他文档更多匹配的好处,而且因为其中一个文档与所有提供的标签相匹配,所以还有一个额外的得分提升,推动它进一步提升排名比只匹配相同数量的标签的东西.
为了解决这个问题,首先要考虑那里的$let表达式为管道中的元素声明一个"变量",这样我们就不会通过$$matchSize在多个位置为结果值键入相同的表达式来"重复自己" .
该变量本身是通过从数组和数组本身计算得到的数组来确定$setIntersection的."交集"的结果只是那些匹配的项目,这为测试该数组提供了空间.searchedTags$tags$size
所以稍后在$size将该匹配归因于"得分"时,通过三元组给出另一个考虑因素$cond,看它$$matchSize是否等于原始长度$tags.如果它是真的那么它$$matchSize被添加到它自身(得分为"标签"长度的两倍),以便与提供的标签"完全匹配",否则该条件的返回结果是0.
处理这两个数字结果,$add产生每个文档的最终总"得分"值.
其主要观点是聚合框架缺少运算符对字符串(如标题)进行任何类型的"模糊匹配".Whist你可以$regex在一个$match阶段匹配,因为这基本上是一个查询运算符,它只会"过滤"结果.
你可以"搞乱",但真正想要的正则表达式是获得匹配条款的数字"得分".这种拆分(尽管在其他语言正则表达式操作符中可能)并不真正可用,因此简单地"标记"您的"标记"以进行输入并将它们与文档"标记"匹配更有意义.
对于"数据库"(MongoDB主要是),这是一个更好的解决方案.或者你甚至可以将它与$text搜索运算符结合起来,用标题的"解析标签"逻辑组合在标题上投射它自己的"得分"值,如下所示.这为"完全匹配"提供了更多的有效性.
它可以与聚合管道一起使用,但即使它本身也不会提供不良结果:
db.items.createIndex({ "title": "text" })
db.items.find({
"$text": { "$search": "iphone 4 64G" } },
{ "score": { "$meta": "textScore" }}
).sort({ "score": { "$meta": "textScore" } })
Run Code Online (Sandbox Code Playgroud)
会产生:
{
"_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
}
Run Code Online (Sandbox Code Playgroud)
但是如果你只想发送字符串并且不想被"标记化"逻辑所困扰,并希望其他逻辑归因于你的"得分",那么请研究专用文本搜索引擎,它比"文本搜索"甚至是像MongoDB这样的主要功能数据库的基本搜索功能.
| 归档时间: |
|
| 查看次数: |
1279 次 |
| 最近记录: |