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

我有一个使用 MongoDB(使用 Mongoose 驱动程序)的 Node/NestJS 后端应用程序。对于“获取”功能,我设置了一个聚合管道,首先可以应用一些“硬”过滤器,完全过滤掉内容 - 现在我想要一些软过滤器,对搜索结果进行排名并过滤掉它们无关的。该算法应该使用文档上的三个字段:标题、描述和标签。标题和标签应该是其中最重要的。如果总相关性得分低于某个阈值,则将排除结果。现在,我已经检查了其他几个 StackOverflow 帖子,例如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 时,项目被完全过滤掉。可以调整值,但本质上,这是我想在聚合框架内实现的算法.它会是什么样子?提前致谢。

【问题讨论】:

    标签: mongodb mongoose aggregation-framework


    【解决方案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 和搜索。
    猜你喜欢
    • 2013-07-23
    • 2013-03-19
    • 1970-01-01
    • 2013-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-16
    相关资源
    最近更新 更多