MongoDB聚合框架:按数组分组去重求和并存储结果的疑问
解决MongoDB按数组分组聚合后存储的问题
嘿,我来帮你搞定这个MongoDB的聚合问题!你想按document数组分组去重,对total字段求和,结果却卡在了$out操作——因为直接把数组作为_id没法正常存入目标集合,对吧?别担心,下面给你两种靠谱的解决方案,既能实现需求,又能顺利把结果存到集合里。
方案一:用哈希值转数组为分组键(MongoDB 4.0+适用)
我们可以先把document数组转换成唯一的哈希字符串,用这个哈希值来分组,最后再保留原数组字段。这样既保证了分组的唯一性,又能避开数组作为_id的问题。
聚合管道代码:
db.yourCollection.aggregate([ // 给每个document数组生成唯一哈希值 { $addFields: { documentHash: { $hash: "$document" } } }, // 按哈希值分组,求和total,同时保留原数组(取第一个就行,同哈希对应同数组) { $group: { _id: "$documentHash", document: { $first: "$document" }, total: { $sum: "$total" } } }, // 生成新的ObjectId作为目标集合的_id,移除临时哈希字段 { $addFields: { _id: ObjectId() } }, // 将结果输出到目标集合(这里命名为uniqueDocuments,你可以改成自己的) { $out: "uniqueDocuments" } ])
为啥这么做?
$hash操作符会把数组转成固定长度的唯一字符串,相同数组生成的哈希值完全一致,不同数组的哈希值几乎不可能碰撞,分组绝对准确。- 分组后用
$first拿原数组,因为同一哈希对应的数组肯定是一模一样的,不会有问题。 - 最后生成标准的
ObjectId当_id,符合MongoDB集合的常规文档结构,后续操作也更方便。
方案二:把数组拼接成字符串分组(兼容低版本MongoDB)
如果你用的MongoDB版本低于4.0,不支持$hash,那就用$reduce把数组里的value字段拼接成一个唯一字符串来当分组键。
聚合管道代码:
db.yourCollection.aggregate([ // 把document数组的value按顺序拼接成字符串,用|当分隔符避免冲突 { $addFields: { documentKey: { $reduce: { input: "$document", initialValue: "", in: { $concat: [ "$$value", { $cond: [{ $eq: ["$$value", ""] }, "", "|"] }, "$$this.value" ] } } } } }, // 按拼接后的字符串分组,求和total,保留原数组 { $group: { _id: "$documentKey", document: { $first: "$document" }, total: { $sum: "$total" } } }, // 生成新的ObjectId作为_id,删掉临时的拼接键 { $addFields: { _id: ObjectId() } }, // 输出到目标集合 { $out: "uniqueDocuments" } ])
小细节提醒:
- 用
|当分隔符是为了避免类似["a", "bc"]和["ab", "c"]这种数组拼接成相同字符串的情况,保证分组的准确性。 - 这个方法兼容性拉满,低版本MongoDB也能完美运行。
结果验证
不管用哪种方案,执行完聚合管道后,去查目标集合uniqueDocuments,就能得到你想要的结果:
{ "_id" : ObjectId("xxx"), "document" : [ { "value" : "a" }, { "value" : "b" }, { "value" : "c" } ], "total" : 9 }, { "_id" : ObjectId("yyy"), "document" : [ { "value" : "b" }, { "value" : "c" } ], "total" : 2 }
内容的提问来源于stack exchange,提问作者Dr. Mza
相关产品推荐
相关产品推荐

