通过与不同权重的相关性查询

use*_*188 4 mongodb mongodb-query aggregation-framework

我提供了产品的搜索功能,

用户可以通过多个标签进行搜索

例如,用户可以搜索"iphone,128G,usa"

如果搜索字词在标题中匹配,则会得到3分,

如果搜索字词在标记中匹配,则会得1分.

我怎么能重写我当前的查询来执行结果.

  • 文件1将获得:7分
  • 文件2将获得:4分

样本文件1

"title": "iphone 6 128G",
"tag": [
  "usa",
  "golden",
]
Run Code Online (Sandbox Code Playgroud)

样本文件2

"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这样的主要功能数据库的基本搜索功能.