MongoDB嵌套分组聚合:如何按年、月、otherObjectId聚合?
实现年→月→otherObjectId的嵌套分组聚合
当然可以实现你想要的嵌套分组结构!你只需要调整聚合的分组层级,把otherObjectId的统计数据嵌套到月份层级下面就行。我帮你修改并拆解完整的聚合语句:
完整聚合语句
db.Collection.aggregate([ // 筛选状态为成功的记录(和你原来的逻辑一致) {$match: {status: 'succeeded'}}, // 第一步分组:按年、月、otherObjectId的组合统计明细 { $group: { _id: { year: {"$year": '$created'}, month: {"$month": '$created'}, otherObjectId: "$otherObjectId" }, count: {$sum: 1}, // 统计该组合下的记录数 totalAmount: {$sum: '$amount'} // 统计该组合下的总金额 } }, // 第二步分组:按年+月聚合,把同一月份的otherObjectId统计整合到一起 { $group: { _id: { year: "$_id.year", month: "$_id.month" }, otherObjectDetails: { $push: { otherObjectId: "$_id.otherObjectId", count: "$count", amount: "$totalAmount" } } } }, // 第三步分组:按年聚合,把同一年的月份数据整合到一起 { $group: { _id: "$_id.year", months: { $push: { month: "$_id.month", otherObjects: "$otherObjectDetails" } } } }, // 可选:调整字段名,让结果结构更贴合你的期望 { $project: { _id: 0, // 去掉默认的_id字段 year: "$_id", // 把年份从_id移到year字段 months: 1 // 保留月份数据 } } ])
各阶段作用拆解
- $match阶段:和你原来的逻辑一致,先筛选出状态为
succeeded的记录,减少后续聚合的数据量。 - 第一次$group:这是最细粒度的分组,以年、月、otherObjectId作为联合主键,计算每个组合下的记录数和总金额,确保每个
otherObjectId在对应年月的统计数据准确。 - 第二次$group:将分组粒度提升到年+月,用
$push把同一个月内的所有otherObjectId统计数据整合到数组中,形成“月份→多个otherObjectId明细”的结构。 - 第三次$group:最后按年分组,把同一年的所有月份数据整合到数组中,最终形成“年→多个月份→每个月份下的otherObjectId明细”的嵌套结构。
- $project阶段:可选步骤,用来优化结果的字段名,让输出更符合你期望的格式(比如去掉默认的
_id,把年份显式命名为year)。
示例返回结果
[ { "year": 2020, "months": [ { "month": 1, "otherObjects": [ { "otherObjectId": ObjectId("60d21b4667d0d8992e610c85"), "count": 4, "amount": 200 }, { "otherObjectId": ObjectId("60d21b4667d0d8992e610c86"), "count": 1, "amount": 50 } ] }, { "month": 2, "otherObjects": [ { "otherObjectId": ObjectId("60d21b4667d0d8992e610c85"), "count": 2, "amount": 150 } ] } ] }, { "year": 2021, "months": [ // 2021年的月份数据... ] } ]
进阶:把otherObjectId作为对象键(可选)
如果你的期望是把otherObjectId作为对象的键(比如{ "60d21b46...": { amount: 200 } }),可以调整第二步分组后的逻辑,用$arrayToObject实现:
// 把原聚合中的第二步$group和后续阶段替换为: { $group: { _id: { year: "$_id.year", month: "$_id.month" }, otherObjectMapEntries: { $push: { k: {$toString: "$_id.otherObjectId"}, // ObjectId不能直接作为键,需转为字符串 v: {amount: "$totalAmount", count: "$count"} } } } }, { $addFields: { otherObjectMap: {$arrayToObject: "$otherObjectMapEntries"} // 把数组转为对象 } }, { $group: { _id: "$_id.year", months: { $push: { month: "$_id.month", otherObjectMap: "$otherObjectMap" } } } }, { $project: { _id: 0, year: "$_id", months: 1 } }
这样返回的每个月份会是一个以otherObjectId字符串为键的对象:
{ "year": 2020, "months": [ { "month": 1, "otherObjectMap": { "60d21b4667d0d8992e610c85": { "amount": 200, "count": 4 }, "60d21b4667d0d8992e610c86": { "amount": 50, "count": 1 } } } ] }
内容的提问来源于stack exchange,提问作者Dean Dumitru
相关产品推荐
相关产品推荐

