如何在Mongoose中按自定义价格规则对产品文档升序排序?
需求说明
我有一个产品模型,其中price是可选字段——因为产品可能存在变体,此时价格会在变体中指定。需要实现产品按价格升序排序,规则如下:
- 若产品存在变体,则取变体中的最低价格参与比较
- 若无变体,则使用产品自身的
price字段值参与比较
产品Schema代码
interface IProductVariation extends IMongooseId { price: number; //... 其他属性 } interface IProduct extends IMongooseId { /* price是可选的,因为如果存在变体,价格会在变体中指定 */ price?: number; variations: IProductVariation[]; //...其他属性 } const variationSchema = new Schema<IProductVariation>({ price: { type: Number, required: true }, // ... }); const productSchema = new Schema<IProduct>({ price: Number, variations: [variationSchema], // ... });
待实现排序逻辑的查询方法
public async getPaginatedData( page: number, perPage: number, query: FilterQuery<IProduct> = {}, sort?: FilterQuery<IProduct> ) { return this.model .find(query) .limit(perPage) .skip((page - 1) * perPage) .sort(sort); }
解决方案
普通的sort方法无法动态计算每个产品的有效排序价格,需要通过MongoDB聚合管道实现需求:
基础实现(仅支持价格升序/降序排序)
直接修改查询方法为聚合方式,动态计算每个产品的有效价格并排序:
public async getPaginatedData( page: number, perPage: number, query: FilterQuery<IProduct> = {}, sortDirection: 1 | -1 = 1 ) { const pipeline = [ // 匹配查询条件 { $match: query }, // 计算用于排序的有效价格 { $addFields: { effectivePrice: { $cond: { // 判断变体数组是否非空 if: { $gt: [{ $size: "$variations" }, 0] }, // 取变体中的最低价格 then: { $min: "$variations.price" }, // 无变体则用自身price字段 else: "$price" } } } }, // 按有效价格排序 { $sort: { effectivePrice: sortDirection } }, // 分页处理 { $skip: (page - 1) * perPage }, { $limit: perPage }, // 移除临时计算的有效价格字段(可选) { $unset: "effectivePrice" } ]; return this.model.aggregate(pipeline); }
兼容原有sort参数的实现
如果需要保留原方法的sort参数,兼容其他字段的排序需求,可以做分支处理:
public async getPaginatedData( page: number, perPage: number, query: FilterQuery<IProduct> = {}, sort?: FilterQuery<IProduct> ) { // 判断是否是按price字段排序的需求 const isPriceSort = sort && Object.keys(sort).includes('price'); if (isPriceSort) { const sortDir = sort.price === -1 ? -1 : 1; const pipeline = [ { $match: query }, { $addFields: { effectivePrice: { $cond: { if: { $gt: [{ $size: "$variations" }, 0] }, then: { $min: "$variations.price" }, else: "$price" } } } }, { $sort: { effectivePrice: sortDir } }, { $skip: (page - 1) * perPage }, { $limit: perPage }, { $unset: "effectivePrice" } ]; return this.model.aggregate(pipeline); } else { // 其他排序需求走原有逻辑 return this.model .find(query) .limit(perPage) .skip((page - 1) * perPage) .sort(sort); } }
关键逻辑说明
$addFields:新增临时字段effectivePrice存储每个产品的排序依据价格$cond:根据变体数组的长度做条件判断,选择排序用的价格来源$min: "$variations.price":MongoDB支持直接从数组的子字段中提取最小值,无需额外遍历
内容的提问来源于stack exchange,提问作者Dima Kambalin
相关产品推荐
相关产品推荐

