使用多个字段在 MongoDB 聚合框架中按相关性排序

Posted

技术标签:

【中文标题】使用多个字段在 MongoDB 聚合框架中按相关性排序【英文标题】:Sorting by relevance in MongoDB aggregation framework using multiple fields 【发布时间】:2021-01-27 04:14:22 【问题描述】:

我有一个使用 MongoDB(使用 Mongoose 驱动程序)的 Node/NestJS 后端应用程序。对于“获取”功能,我设置了一个聚合管道,首先可以应用一些“硬”过滤器,完全过滤掉内容 - 现在我想要一些软过滤器,对搜索结果进行排名并过滤掉它们无关的。该算法应该使用文档上的三个字段:标题、描述和标签。标题和标签应该是其中最重要的。如果总相关性得分低于某个阈值,则将排除结果。现在,我已经检查了其他几个 *** 帖子,例如this one,但它们似乎都与“标签”字段有关。我找到了suggested to use indexes for this 的一个文档,但如果我大致知道该怎么做,我最好希望通过聚合框架来做。

下面是另一个应用程序的代码,用于演示该功能;

        do 
          let reg
          if (Array.isArray(searchString)) 
            reg = new RegExp(searchString[i], 'gi')
           else 
            reg = new RegExp(searchString, 'gi')
          
          for (const note of this.notes) 
            const countTitle = (note.title.match(reg) || []).length
            note.searchScore += countTitle

            let countTags = 0

            for (const tag of note.tags) 
              const tagLength = (tag.match(reg) || []).length
              countTags += tagLength
            

            note.searchScore += countTags * 0.5

            const countContent = (note.content.match(reg) || []).length

            note.searchScore += countContent * 0.3
          
          i++
         while (!Array.isArray(searchString) && i < searchString.length)
        this.toDisplay = this.notes.filter(
          f => f.searchScore > 0 + searchString.length / 4
        )
        this.showNew = false
        this.sortUp = false
        this.sortItems('relevance')
       else 
        this.updateUI()
      
    

上面的算法接受一个字符串或字符串数​​组。标题、标签和描述/内容的权重分别为 1、0.5 和 0.3。设置了一个阈值,当分数低于或等于 0 + 搜索词的数量除以 4 时,项目被完全过滤掉。可以调整值,但本质上,这是我想在聚合框架内实现的算法.它会是什么样子?提前致谢。

【问题讨论】:

【参考方案1】:

您可以在聚合中使用文本索引 - 但它必须是第一阶段。

这是我的看法,只有一个搜索词:

const search = new RegExp(searchString, 'i');

collection.aggregate().match(hardFilters)
  // This step is not really necessary
  .match(
    $or: [
      tags: search
    , 
      title: search
    , 
      content: search
    ]
  )
  .set(
    relevance: 
      $sum: [
          $multiply: [$size: $regexFindAll: input: "$title", regex: search, 100],
          $multiply: [$size: $regexFindAll: input: 
              $reduce: 
                 input: "$tags",
                 initialValue: "",
                 in:  $concat : ["$$value", " ", "$$this"] 
              
          , regex: search, 50],
          $multiply: [$size: $regexFindAll: input: "$content", regex: search, 30],
      ]
    
  )
  .match(relevance: $gte: searchString.length * 25)
  .sort(relevance: -1);

如果有多个搜索词,也许你可以这样做:

const search = new RegExp(searchStrings.join('|'), 'i');

如果您真的需要,可以单独搜索每个标签,方法是:

    relevance: 
      $sum: [].concat(...searches.map(search => [
          $multiply: [$size: $regexFindAll: input: "$title", regex: search, 100],
          $multiply: [$size: $regexFindAll: input: ..., regex: search, 50],
          $multiply: [$size: $regexFindAll: input: "$content", regex: search, 30],
      ]))
    

也许您可以添加边界检查,无论是多次搜索还是单次搜索:

const search = new RegExp("\b" + searchStrings.join('|') + "\b", 'i');

【讨论】:

好吧,由于 4.0 不支持聚合中的 $set 方法,您强迫我将 MongoDB 更新到 4.2:P 我立即注意到第一个块的一个问题。标签作为字符串数组存储在数据库中。这当然可以改变......但不会像 JS/TS .join() 方法那样将字符串连接在一起? $concatArray 给了我一个类似的错误:“$regexFindAll 需要 'input' 为字符串类型”。 当前查询的哪一部分失败了?我在分配相关性时使用 $concat,第一个可选的 $match 也应该可以工作 在这一行特别是:$multiply: [$size: $regexFindAll: input: $concat: "$tags", regex: searchString, 50],我在这里将“search”更改为“searchString”,因为 search 是来自路由的未处理输入流。抛出的错误如下:“$concat 只支持字符串,不支持数组”你使用 MongoDB 4.4 吗?我刚刚更新到4.2。不知道$concat的行为在4.4有没有改变,如果有的话,我可能要再次更新了。 你确定你使用的是 $concat 而不是 $concatArrays? @Saddex 你是对的。这对我来说似乎是一个错误。无论如何更新的答案应该是好的!【参考方案2】:

鉴于 Atlas Search 默认返回按相关性排序的文档并使用倒排索引,这似乎是这里的工作工具。相关性会更好,更可定制。根据您正在构建的内容,您还可以获得其他可能会从中受益的功能,例如突出显示和自动完成。

【讨论】:

这是一个很好的提示,我将来可能会考虑,但我现在坚持在我的 DigitalOcean VPS 上本地安装 MongoDB。还是谢谢! @Saddex 我认为这很有意义。当您开始时,如果您有任何问题,请在此处或其他地方联系我。我很乐意提供帮助。我喜欢 MongoDB 和搜索。

以上是关于使用多个字段在 MongoDB 聚合框架中按相关性排序的主要内容,如果未能解决你的问题,请参考以下文章

在猫鼬聚合框架中按日期排序

使用mongoose在mongodb中按升序和降序对多个字段进行排序

如何在 mongoDb 中按多个字段排序

在 MongoDB 中按年和月聚合查询

如何在mongodb聚合中按顺序获取计数?

如何使用 mongodb 聚合添加自定义字段?