MongoDB聚合计算餐厅支付极值两国人数占比方案咨询
MongoDB餐厅消费统计比值计算方案
people集合数据格式示例
{ _id: ObjectId("576865c1bab6cf2f2fb39d8f"), firstName: 'Zofia', lastName: 'Wojciechowska', sex: 'female', address: { country: 'Poland', city: 'Warsaw', postalCode: '05-075', street: 'Wilcza' }, birthDate: ISODate("1974-11-18T12:11:00.000Z"), payments: [ { category: 'relax', name: 'holidays', amount: 282.46 }, { category: 'food', name: 'store', amount: 15.18 }, { category: 'food', name: 'store', amount: 64.09 }, { category: 'food', name: 'restaurant', amount: 26.32 }, { category: 'health', name: 'pharmacy', amount: 53.27 }, { category: 'food', name: 'restaurant', amount: 43.56 }, { category: 'health', name: 'doctor', amount: 142.27 }, { category: 'relax', name: 'holidays', amount: 959.59 }, { category: 'health', name: 'pharmacy', amount: 61.14 }, { category: 'health', name: 'doctor', amount: 102.29 } ], wealth: { bankAccounts: [ { bank: 'Pekao SA', balance: 18643.26 } ], realEstates: [ { type: 'flat', worth: 599000 }, { type: 'flat', worth: 684000 } ], market: { stocks: [], bonds: [] }, credits: [ { bank: 'Bank Zachodni WBK', type: 'mortgage', value: 499985.3 } ] } }
注:上述样例文档存在字段截断,不影响聚合逻辑编写
需求说明
- 基础需求:计算两类国家的人员数量比值
- Max国家:
payments.name = 'restaurant'支付记录平均金额最高的国家 - Min国家:
payments.name = 'restaurant'支付记录平均金额最低的国家
- Max国家:
- 进阶需求:计算比值时,Max国家仅统计个人餐厅类支付金额高于Min国家餐厅类支付均值的人员,Min国家统计规则不变。
原有基础代码存在的问题
之前写的聚合逻辑有两个核心错误,导致无法实现进阶需求:
- 第二次
$group的_id字段取值错误,上一步分组输出已经没有$address.country字段,会导致分组无法拿到全局的Max、Min国家 - 没有预留Max国家人员的金额过滤逻辑,无法筛选出符合进阶要求的统计人数
原有代码如下:
db.people.aggregate([ { $unwind: "$payments" }, { $match: { "payments.name": "restaurant" } }, { $group: { _id: { country: "$address.country" }, avgPayment: { $avg: "$payments.amount" }, noPerson: { $sum: 1} } }, { $sort : {"avgPayment": -1}}, { $group: { _id: { country: "$address.country" }, max: {$first: "$$ROOT"}, min: { $last: "$$ROOT"} }}, { $project: {res: {$divide: [ "$max.noPerson","$min.noPerson" ]}, _id: 0}} ])
可直接运行的实现方案
用$facet单阶段完成多维度统计,避免多次扫表,逻辑如下:
db.people.aggregate([ // 拆分支付数组,仅保留餐厅消费记录 { $unwind: "$payments" }, { $match: { "payments.name": "restaurant" } }, { $facet: { // 分支1:计算每个国家餐厅消费均值,定位Max、Min国家 countryStat: [ { $group: { _id: "$address.country", avgRestaurantPay: { $avg: "$payments.amount" } } }, { $sort: { avgRestaurantPay: -1 } }, { $group: { _id: null, maxCountry: { $first: "$$ROOT" }, minCountry: { $last: "$$ROOT" } } } ], // 分支2:保留所有餐厅消费记录的核心字段用于后续人数统计 userPayRecords: [ { $project: { country: "$address.country", payAmount: "$payments.amount" } } ] } }, // 展开计算结果,关联国家统计值到每条消费记录 { $unwind: "$countryStat" }, { $unwind: "$userPayRecords" }, { $replaceRoot: { newRoot: { $mergeObjects: ["$userPayRecords", "$countryStat"] } } }, // 按规则统计有效人数 { $group: { _id: null, // Min国家人数:所有属于Min国家的餐厅消费记录数 minCountryTotal: { $sum: { $cond: [{ $eq: ["$country", "$minCountry._id"] }, 1, 0] } }, // Max国家有效人数:属于Max国家,且单笔餐厅消费额高于Min国家餐厅消费均值 maxCountryValid: { $sum: { $cond: [ { $and: [ { $eq: ["$country", "$maxCountry._id"] }, { $gt: ["$payAmount", "$minCountry.avgRestaurantPay"] } ] }, 1, 0 ] } } } }, // 计算最终比值,输出辅助校验字段 { $project: { ratio: { $divide: ["$maxCountryValid", "$minCountryTotal"] }, maxCountryName: "$maxCountry._id", minCountryName: "$minCountry._id", minCountryAvgPay: "$minCountry.avgRestaurantPay", _id: 0 } } ])
逻辑说明
- 用
$facet在一次聚合扫描中同时完成国家维度均值计算、个人消费记录留存,性能比多段聚合高 - 修正了原代码二次分组的_id错误,统一按
_id:null分组拿到全局最高、最低均值的国家信息 - 统计Max国家人数时增加双重判断:国籍属于Max国家+个人餐厅消费额大于Min国家餐厅消费均值,完全符合进阶需求
- 输出结果额外返回Max/Min国家名称、Min国家餐厅消费均值,方便结果校验
内容的提问来源于stack exchange,提问作者An Minh
相关产品推荐
相关产品推荐

