使用多个字段在 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 聚合框架中按相关性排序的主要内容,如果未能解决你的问题,请参考以下文章