从另一个集合中的数据提高搜索分数

发布于 2025-01-23 05:44:22 字数 3519 浏览 1 评论 0 原文

我使用地图集搜索返回文档列表(使用猫鼬):

const searchResults = await Resource.aggregate()
   .search({
       text: {
           query: searchQuery,
           path: ["title", "tags", "link", "creatorName"], 
       },
   }
   )
   .match({ approved: true })
   .addFields({
       score: { $meta: "searchScore" }
   })
   .exec();

这些资源可以由用户向上和倾斜(例如Stackoverflow上的问题)。我想根据这些投票提高搜索分数。

我可以使用 boost> boost 操作员。

问题:投票不是资源文档的属性。取而代之的是,它们存储在一个单独的集合中:

const resourceVoteSchema = mongoose.Schema({
    _id: { type: String },
    userId: { type: mongoose.Types.ObjectId, required: true },
    resourceId: { type: mongoose.Types.ObjectId, required: true },
    upDown: { type: String, required: true },

在上面获得搜索结果后,我会分别获取投票并将其添加到每个搜索结果中:

for (const resource of searchResults) {
    const resourceVotes = await ResourceVote.find({ resourceId: resource._id }).exec();
    resource.votes = resourceVotes
}

我然后从客户端上的upvotes中减去下降票在UI中显示最终数字。

如何将此投票点价值纳入搜索结果的分数?我是否必须在客户端重新排序?

编辑:

这是我的更新代码。唯一缺少的部分是让资源投票提高搜索分数,同时将所有资源访问量文档保留在 fotes field 以便我以后可以访问它们。我正在使用Mongoose语法,但是与普通MongoDB语法的答案对我有用:

const searchResults = await Resource.aggregate()
            .search({
                compound: {
                    should: [
                        {
                            wildcard: {
                                query: queryStringSegmented,
                                path: ["title", "link", "creatorName"],
                                allowAnalyzedField: true,
                            }
                        },
                        {
                            wildcard: {
                                query: queryStringSegmented,
                                path: ["topics"],
                                allowAnalyzedField: true,
                                score: { boost: { value: 2 } },
                            }
                        }
                        ,
                        {
                            wildcard: {
                                query: queryStringSegmented,
                                path: ["description"],
                                allowAnalyzedField: true,
                                score: { boost: { value: .2 } },
                            }
                        }
                    ]
                }
            }
            )
            .lookup({
                from: "resourcevotes",
                localField: "_id",
                foreignField: "resourceId",
                as: "votes",
            })
            .addFields({
                searchScore: { $meta: "searchScore" },
            })
            .facet({
                approved: [
                    { $match: matchFilter },
                    { $skip: (page - 1) * pageSize },
                    { $limit: pageSize },
                ],
                resultCount: [
                    { $match: matchFilter },
                    { $group: { _id: null, count: { $sum: 1 } } }
                ],
                uniqueLanguages: [{ $group: { _id: null, all: { $addToSet: "$language" } } }],
            })
            .exec();

I use Atlas Search to return a list of documents (using Mongoose):

const searchResults = await Resource.aggregate()
   .search({
       text: {
           query: searchQuery,
           path: ["title", "tags", "link", "creatorName"], 
       },
   }
   )
   .match({ approved: true })
   .addFields({
       score: { $meta: "searchScore" }
   })
   .exec();

These resources can be up and downvoted by users (like questions on Stackoverflow). I want to boost the search score depending on these votes.

I can use the boost operator for that.

Problem: The votes are not a property of the Resource document. Instead, they are stored in a separate collection:

const resourceVoteSchema = mongoose.Schema({
    _id: { type: String },
    userId: { type: mongoose.Types.ObjectId, required: true },
    resourceId: { type: mongoose.Types.ObjectId, required: true },
    upDown: { type: String, required: true },

After I get my search results above, I fetch the votes separately and add them to each search result:

for (const resource of searchResults) {
    const resourceVotes = await ResourceVote.find({ resourceId: resource._id }).exec();
    resource.votes = resourceVotes
}

I then subtract the downvotes from the upvotes on the client and show the final number in the UI.

How can I incorporate this vote points value into the score of the search results? Do I have to reorder them on the client?

Edit:

Here is my updated code. The only part that's missing is letting the resource votes boost the search score, while at the same time keeping all resource-votes documents in the votes field so that I can access them later. I'm using Mongoose syntax but an answer with normal MongoDB syntax will work for me:

const searchResults = await Resource.aggregate()
            .search({
                compound: {
                    should: [
                        {
                            wildcard: {
                                query: queryStringSegmented,
                                path: ["title", "link", "creatorName"],
                                allowAnalyzedField: true,
                            }
                        },
                        {
                            wildcard: {
                                query: queryStringSegmented,
                                path: ["topics"],
                                allowAnalyzedField: true,
                                score: { boost: { value: 2 } },
                            }
                        }
                        ,
                        {
                            wildcard: {
                                query: queryStringSegmented,
                                path: ["description"],
                                allowAnalyzedField: true,
                                score: { boost: { value: .2 } },
                            }
                        }
                    ]
                }
            }
            )
            .lookup({
                from: "resourcevotes",
                localField: "_id",
                foreignField: "resourceId",
                as: "votes",
            })
            .addFields({
                searchScore: { $meta: "searchScore" },
            })
            .facet({
                approved: [
                    { $match: matchFilter },
                    { $skip: (page - 1) * pageSize },
                    { $limit: pageSize },
                ],
                resultCount: [
                    { $match: matchFilter },
                    { $group: { _id: null, count: { $sum: 1 } } }
                ],
                uniqueLanguages: [{ $group: { _id: null, all: { $addToSet: "$language" } } }],
            })
            .exec();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

帅哥哥的热头脑 2025-01-30 05:44:22

它只能使用一个查询来完成,看起来与以下查询相似:

Resource.aggregate([
  {
    $search: {
      text: {
        query: "searchQuery",
        path: ["title", "tags", "link", "creatorName"]
      }
    }
  },
  {$match: {approved: true}},
  {$addFields: {score: {$meta: "searchScore"}}},
  {
    $lookup: {
      from: "ResourceVote",
      localField: "_id",
      foreignField: "resourceId",
      as: "votes"
    }
  }
])

使用 $ lookup 步骤,

如果您想使用票数来提高得分,您可以用以下内容替换上述 $ lookup 步骤:

{
    $lookup: {
      from: "resourceVote",
      let: {resourceId: "$_id"},
      pipeline: [
        {
          $match: {$expr: {$eq: ["$resourceId", "$resourceId"]}}
        },
        {
          $group: {
            _id: 0,
            sum: {$sum: {$cond: [{$eq: ["$upDown", "up"]}, 1, -1]}}
          }
        }
      ],
      as: "votes"
    }
  },
  {$addFields: { votes: {$arrayElemAt: ["$votes", 0]}}},
  {
    $project: {
      "wScore": {
        $ifNull: [
          {$multiply: ["$score", "$votes.sum"]},
          "$score"
        ]
      },
      createdAt: 1,
      score: 1
    }
  }

您可以在

编辑:如果您想对结果进行选票,则可以做类似的事情:

db.searchResults.aggregate([
   {
    $lookup: {
      from: "ResourceVote",
      localField: "_id",
      foreignField: "resourceId",
      as: "votes"
    }
  },
  {
    "$addFields": {
      "votesCount": {
        $reduce: {
          input: "$votes",
          initialValue: 0,
          in: {$add: ["$value", {$cond: [{$eq: ["$this.upDown", "up"]}, 1, -1]}]}
        }
      }
    }
  },
  {
    $addFields: {
      "wScore": {
        $add: [{$multiply: ["$votesCount", 0.1]}, "$score"]
      }
    }
  }
])

可以看到在这里

It could be done with one query only, looking similar to:

Resource.aggregate([
  {
    $search: {
      text: {
        query: "searchQuery",
        path: ["title", "tags", "link", "creatorName"]
      }
    }
  },
  {$match: {approved: true}},
  {$addFields: {score: {$meta: "searchScore"}}},
  {
    $lookup: {
      from: "ResourceVote",
      localField: "_id",
      foreignField: "resourceId",
      as: "votes"
    }
  }
])

Using the $lookup step to get the votes from the ResourceVote collection

If you want to use the votes to boost the score, you can replace the above $lookup step with something like:

{
    $lookup: {
      from: "resourceVote",
      let: {resourceId: "$_id"},
      pipeline: [
        {
          $match: {$expr: {$eq: ["$resourceId", "$resourceId"]}}
        },
        {
          $group: {
            _id: 0,
            sum: {$sum: {$cond: [{$eq: ["$upDown", "up"]}, 1, -1]}}
          }
        }
      ],
      as: "votes"
    }
  },
  {$addFields: { votes: {$arrayElemAt: ["$votes", 0]}}},
  {
    $project: {
      "wScore": {
        $ifNull: [
          {$multiply: ["$score", "$votes.sum"]},
          "$score"
        ]
      },
      createdAt: 1,
      score: 1
    }
  }

As you can see on this playground example

EDIT: If you want to keep the votes on the results, you can do something like:

db.searchResults.aggregate([
   {
    $lookup: {
      from: "ResourceVote",
      localField: "_id",
      foreignField: "resourceId",
      as: "votes"
    }
  },
  {
    "$addFields": {
      "votesCount": {
        $reduce: {
          input: "$votes",
          initialValue: 0,
          in: {$add: ["$value", {$cond: [{$eq: ["$this.upDown", "up"]}, 1, -1]}]}
        }
      }
    }
  },
  {
    $addFields: {
      "wScore": {
        $add: [{$multiply: ["$votesCount", 0.1]}, "$score"]
      }
    }
  }
])

As can be seen here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文