按不同权重的相关性进行查询



我提供了产品搜索功能,

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

例如,用户可以搜索"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这样的主要功能数据库的"文本搜索"甚至基本搜索能力都要好得多。

相关内容

  • 没有找到相关文章

最新更新