MongoDB跨Transaction与Category集合构建聚合管道:获取支出类Top3分类占比
MongoDB交易分类占比统计聚合方案
需求
- 基于MongoDB的
Transaction和Category集合构建聚合管道 Transaction集合字段:amount(金额)、categoryID(分类ID)、description(描述)Category集合字段:type(分类类型)、icon(图标)、color(颜色)- 核心要求:筛选出分类类型为
Expense的交易,计算占比最高的3个分类及「其他」分类的金额占比,且金额仅存储在Transaction集合中
首次尝试(不符合金额存储要求)
最初直接基于Category集合聚合,但忽略了金额仅存在于Transaction集合的要求,逻辑错误:
Category.aggregate([ { $match: { type: 'expense' } }, { $group: { _id: "$name", amount: { $sum: "$amount" } } }, { $group: { _id: null, totalExpense: { $sum: "$amount" }, categories: { $push: { name: "$_id", amount: "$amount" } } } }, { $project: { _id: 0, categories: { $map: { input: "$categories", as: "category", in: { name: "$$category.name", percent: { $multiply: [{ $divide: ["$$category.amount", "$totalExpense"] }, 100] } } } } } }, { $unwind: "$categories" }, { $sort: { "categories.percent": -1 } }, { $limit: 3 } ])
二次尝试(返回空数组)
改为基于Transaction集合关联聚合,但因逻辑错误返回空数组,主要问题:
- 按
category.type分组会将所有Expense交易归为同一组,无法统计单个分类的占比 - 在
$project阶段错误使用聚合操作符$sum,该阶段不支持此类计算
Transaction.aggregate([ // 关联Transaction与Category集合 { $lookup: { from: 'Category', localField: 'categoryID', foreignField: '_id', as: 'category', }, }, // 展开category数组 { $unwind: '$category', }, // 筛选分类类型为Expense的交易 { $match: { 'category.type': 'Expense', }, }, // 按分类类型分组并计算占比(逻辑错误) { $group: { _id: '$category.type', total: { $sum: '$amount' }, count: { $sum: 1 }, }, }, { $project: { _id: 0, category: '$_id', percentage: { $multiply: [{ $divide: ['$count', { $sum: '$count' }] }, 100], }, }, }, // 按占比降序排序 { $sort: { percentage: -1 }, }, // 取前3个分类 { $limit: 3, }, // 将剩余分类合并为其他(逻辑错误) { $group: { _id: null, top3: { $push: '$$ROOT' }, others: { $sum: { $subtract: [100, { $sum: '$top3.percentage' }] } }, }, }, { $project: { top3: 1, others: { category: 'Others', percentage: '$others' }, }, }, ])
最终可行聚合管道
以下管道满足所有需求,关键步骤包括:转换categoryID类型确保关联成功、按分类名称分组计算总金额、排序后取前3并计算「其他」分类的占比:
Transaction.aggregate([ { $match: { userID: { $eq: UserID }, type: 'Expense', }, }, // 将categoryID转换为ObjectId,确保与Category集合的_id类型匹配 { $addFields: { categoryID: { $toObjectId: '$categoryID' } }, }, // 关联Category集合获取分类信息 { $lookup: { from: 'categories', localField: 'categoryID', foreignField: '_id', as: 'category_info', }, }, // 展开分类信息数组 { $unwind: '$category_info', }, // 按分类名称分组,计算每个分类的总交易金额 { $group: { _id: '$category_info.name', amount: { $sum: '$amount' }, }, }, // 按总金额降序排序 { $sort: { amount: -1, }, }, // 计算所有Expense交易的总金额,并保存各分类数据 { $group: { _id: null, total: { $sum: '$amount' }, data: { $push: '$$ROOT' }, }, }, // 生成前3个分类的占比数据,以及「其他」分类的占比 { $project: { results: { $map: { input: { $slice: ['$data', 3], }, in: { category: '$$this._id', percentage: { $round: { $multiply: [{ $divide: ['$$this.amount', '$total'] }, 100], }, }, }, }, }, others: { $cond: { if: { $gt: [{ $size: '$data' }, 3] }, then: { amount: { $subtract: [ '$total', { $sum: { $slice: ['$data.amount', 3], }, }, ], }, percentage: { $round: { $multiply: [ { $divide: [ { $subtract: [ '$total', { $sum: { $slice: ['$data.amount', 3] } }, ], }, '$total', ], }, 100, ], }, }, }, else: { amount: null, percentage: null, }, }, }, }, }, ])
内容的提问来源于stack exchange,提问作者Pranav Ragji
相关产品推荐
相关产品推荐

