如何在Mongoose.js中按自动计算字段排序并自动更新该字段
解决Mongoose中自动计算产品评分字段的问题
针对你遇到的rating字段仅初始化时计算、更新时不自动更新的问题,有两种可行方案,根据你的排序需求选择:
方案一:使用Mongoose中间件自动维护rating字段(推荐用于需要频繁排序的场景)
这种方式会把rating实际存储到数据库,更新sumOfRatings或numOfRatings时自动重新计算,适合需要基于rating快速排序的场景。
1. 修改Schema定义
先移除rating的默认函数,改为默认值0(避免除以0导致NaN):
const productSchema = new Schema({ title: { type: String, required: true }, views: { type: Number, default: 0 }, sumOfRatings: { type: Number, default: 0 }, numOfRatings: { type: Number, default: 0 }, rating: { type: Number, default: 0 } }, { collection: "Products", timestamps: true });
2. 添加pre('save')中间件处理创建/手动保存操作
这个钩子会在调用save()方法时触发,处理文档创建和手动更新的场景:
productSchema.pre('save', function(next) { // 处理评分次数为0的情况,避免出现NaN this.rating = this.numOfRatings === 0 ? 0 : this.sumOfRatings / this.numOfRatings; // 可选:保留两位小数优化显示 // this.rating = Math.round((this.sumOfRatings / this.numOfRatings) * 100) / 100; next(); });
3. 添加pre('findOneAndUpdate')中间件处理更新操作
对于findOneAndUpdate、findByIdAndUpdate这类直接更新的方法,pre('save')不会触发,需要单独添加钩子:
productSchema.pre('findOneAndUpdate', async function(next) { const update = this.getUpdate(); // 仅当sumOfRatings或numOfRatings被更新时才重新计算rating if (update.sumOfRatings !== undefined || update.numOfRatings !== undefined) { // 获取当前文档的原始数据 const product = await this.model.findOne(this.getQuery()); const newSum = update.sumOfRatings ?? product.sumOfRatings; const newNum = update.numOfRatings ?? product.numOfRatings; const newRating = newNum === 0 ? 0 : newSum / newNum; // 将rating加入更新字段 this.setUpdate({ ...update, rating: newRating }); } next(); });
4. 可选:添加pre('updateMany')处理批量更新
如果有批量更新需求,补充以下钩子:
productSchema.pre('updateMany', async function(next) { const update = this.getUpdate(); if (update.sumOfRatings !== undefined || update.numOfRatings !== undefined) { // 批量更新时需要遍历匹配的文档,逐个计算rating const products = await this.model.find(this.getQuery()); const updates = products.map(product => { const newSum = update.sumOfRatings ?? product.sumOfRatings; const newNum = update.numOfRatings ?? product.numOfRatings; const newRating = newNum === 0 ? 0 : newSum / newNum; return { updateOne: { filter: { _id: product._id }, update: { ...update, rating: newRating } } }; }); await this.model.bulkWrite(updates); // 阻止原updateMany执行,因为已经用bulkWrite处理了 this._updatePromise = Promise.resolve(); } next(); });
方案二:使用聚合查询实时计算rating(适合数据量较小的场景)
如果不想存储rating字段,可以在查询时通过MongoDB聚合管道实时计算并排序,无需维护字段,但每次查询都要计算,性能略差:
const sortedProducts = await Product.aggregate([ { $addFields: { rating: { $cond: { if: { $eq: ["$numOfRatings", 0] }, then: 0, else: { $divide: ["$sumOfRatings", "$numOfRatings"] } } } } }, { $sort: { rating: -1 } } // 按评分降序排序 ]);
内容的提问来源于stack exchange,提问作者M.KH
相关产品推荐
相关产品推荐

