如何针对可变长度数组应用MongoDB的$group聚合操作
嘿,我来帮你搞定MongoDB里带可变长度数组的文档的$group聚合操作!先把你的文档结构贴出来方便参考:
{ "_id" : ObjectId("5ac392f15c592414ec99d630"), "nv" : "de", "nid" : 1.0, "coc" : 3.0, "seg" : { "segid" : 0.0, "attv" : [ { "id" : 1.0, "v" : 5.0 }, { "id" : 2.0, "v" : 4.0 } ], "devattv" : [ { "id" : 1.0, "v" : 2.0, "t" : 1.0 }, { "id" : 1.0, "v" : 3.0, "t" : ... } ] } }
下面分几种常见场景给你拆解,一步步来:
场景1:按顶层字段分组,聚合数组内的统计值
比如你想按nv字段分组,计算所有seg.attv.v的总和、平均值,或者统计数组元素的总数量。这时候需要先用$unwind把数组拆成单个文档,再做分组聚合:
db.collection.aggregate([ // 把attv数组拆分成独立文档,每个数组元素对应一条新文档 { $unwind: "$seg.attv" }, // 按顶层的nv字段分组,聚合数组内的数值 { $group: { _id: "$nv", // 分组键,这里选顶层的nv字段 totalAttvValue: { $sum: "$seg.attv.v" }, // 所有attv.v的总和 avgAttvValue: { $avg: "$seg.attv.v" }, // 所有attv.v的平均值 totalAttvItems: { $sum: 1 } // 统计所有attv数组元素的总数量 } } ])
小提示:如果数组可能为空,默认$unwind会丢弃空数组的文档,你可以加参数保留它们:{ $unwind: { path: "$seg.attv", preserveNullAndEmptyArrays: true } }
场景2:按数组内的字段分组,关联其他字段聚合
比如你想按seg.attv.id分组,计算每个id对应的v的总和,同时收集对应的顶层nid值:
db.collection.aggregate([ { $unwind: "$seg.attv" }, { $group: { _id: "$seg.attv.id", // 按数组内的id字段作为分组键 totalV: { $sum: "$seg.attv.v" }, // 该id对应的v值总和 distinctNids: { $addToSet: "$nid" } // 收集关联的顶层nid,自动去重 } } ])
场景3:不拆分数组,直接聚合嵌套数组内容
如果不想用$unwind导致数据膨胀(比如数组元素很多时),可以用$map、$reduce这类数组操作符,在文档内部先处理数组,再做分组:
比如按nid分组,把每个文档的devattv数组按id聚合v的总和:
db.collection.aggregate([ { $group: { _id: "$nid", // 先把每个文档的devattv按id合并v值,再收集到数组里 tempDevattv: { $push: { $reduce: { input: "$seg.devattv", initialValue: {}, in: { id: "$$this.id", totalV: { $add: ["$$value.totalV", "$$this.v"] } } } } } } }, // 再把临时数组里的同id数据合并最终总和 { $addFields: { finalDevattv: { $reduce: { input: "$tempDevattv", initialValue: [], in: { $concatArrays: [ "$$value", [{ id: "$$this.id", totalV: "$$this.totalV" }] ] } } } } } ])
关键注意事项
- 多次
$unwind多个数组会产生笛卡尔积,数据量会大幅增加,谨慎使用; - 对于超大规模的数组聚合,优先考虑
$unwind+$group的组合,性能更优; - 如果需要保留原数组结构,再结合
$push、$addToSet把聚合后的元素重新组装成数组。
内容的提问来源于stack exchange,提问作者Alex P.
相关产品推荐
相关产品推荐

