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

时间:2015-07-25 08:44:07

标签: mongodb mongodb-query aggregation-framework

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

用户可以按多个标签搜索

例如,用户可以搜索“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 } }
  )

1 个答案:

答案 0 :(得分:9)

我认为你正在以错误的方式接近这一点并且过多地询问"模糊匹配"来自数据库。相反,请考虑此修订数据样本:

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被添加到它自己(得分加倍"标记"长度)以成为"完全匹配"提供的标签,否则该条件的返回结果为db.items.createIndex({ "title": "text" }) db.items.find({ "$text": { "$search": "iphone 4 64G" } }, { "score": { "$meta": "textScore" }} ).sort({ "score": { "$meta": "textScore" } })

使用$add处理这两个数字结果会产生最终总数"得分"每个文件的价值。

这一点的主要观点是聚合框架缺乏运营商进行任何类型的模糊匹配"在诸如标题之类的字符串上。您可以在$regex阶段$match匹配,因为这基本上是一个查询运算符,它只会"过滤"结果

你可以"乱搞"有了它,但你真正想要的正则表达式是得到一个数字"得分"对于匹配的条款。这样的分裂(尽管在其他语言正则表达式运算符中可能)并不真正可用,因此简单地“标记化”#34;你的"标签"输入并将它们与文档"标记"。

匹配

对于"数据库" (MongoDB主要是)这是一个更好的解决方案。或者,您甚至可以将其与$text搜索运算符结合起来,以预测它自己的"得分"使用"解析标签"的组合对标题赋值逻辑如此处所示。这为"完全匹配提供了更多的有效性"。

它可以与聚合管道一起使用,但即使它本身也不会产生不良结果:

{
    "_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
}

会产生:

renderer.setClearColor( 0xffffff, 1);

但是如果你只是想发送字符串而不想被"标记化"逻辑,并希望其他逻辑归因于您的"得分",然后查看专用文本搜索引擎,这比#"文本搜索"更好。甚至是像MongoDB这样的主要功能数据库的基本搜索功能。

相关问题