MongoDB Aggregate:利用Facet&Group实现动态Key的状态统计分组
按key分组统计动态status数量的MongoDB实现
问题背景
给定MongoDB数据集,需按key字段分组,统计每组内不同status的数量。key和status均为动态值,后续可能新增。数据集示例如下:
[ { "_id": ObjectId("635f808302d5f6cb7c293298"), "key": "scientific", "custom_properties": [ { "namespace": "common metadata", "key": "status", "scope": "public", "value": "To be uploaded", "inherited": null }, { "namespace": "common metadata", "key": "version", "scope": "public", "value": "1.0", "inherited": null }, { "namespace": "common metadata", "key": "start date", "scope": "public", "value": "1642550400000", "inherited": null } ] }, { "_id": ObjectId("635f809d02d5f6cb7c29353c"), "key": "contracts", "custom_properties": [ { "namespace": "common metadata", "key": "status", "scope": "public", "value": "To be reviewed", "inherited": null }, { "namespace": "common metadata", "key": "version", "scope": "public", "value": "", "inherited": null }, { "namespace": "contracts", "key": "expiry date", "scope": "public", "value": "", "inherited": null }, { "namespace": "common metadata", "key": "start date", "scope": "public", "value": "", "inherited": null } ] }, { "_id": ObjectId("635f80a002d5f6cb7c293588"), "key": "contracts", "custom_properties": [ { "namespace": "common metadata", "key": "status", "scope": "public", "value": "To be uploaded", "inherited": null }, { "namespace": "common metadata", "key": "version", "scope": "public", "value": "", "inherited": null } ] } ]
期望输出:
[ { "scientific": [ { "To be reviewed": 0, "To be uploaded": 1 } ], "contracts": [ { "To be reviewed": 1, "To be uploaded": 1 } ] } ]
解决方案
不用纠结$facet,核心思路是先提取每个文档的status值,再按key分层统计,最后动态构造结果结构。以下提供两种实现方案:
方案一:基础统计(仅显示存在的status)
适合不需要显示数量为0的status场景,步骤清晰高效:
db.collection.aggregate([ // 从custom_properties中提取status值 { $project: { key: 1, status: { $arrayElemAt: [ { $map: { input: { $filter: { input: "$custom_properties", cond: { $eq: ["$$this.key", "status"] } } }, as: "prop", in: "$$prop.value" } }, 0 ] } } }, // 按key+status分组统计数量 { $group: { _id: { key: "$key", status: "$status" }, count: { $sum: 1 } } }, // 按key聚合所有status统计结果 { $group: { _id: "$_id.key", statusCounts: { $push: { k: "$_id.status", v: "$count" } } } }, // 转换为{ key: { status: count } }结构 { $project: { _id: 0, keyVal: { $arrayToObject: [ [ { k: "$_id", v: { $arrayToObject: "$statusCounts" } } ] ] } } }, // 合并所有结果为一个对象 { $group: { _id: null, result: { $mergeObjects: "$keyVal" } } }, // 输出最终结果 { $project: { _id: 0, result: 1 } } ])
输出结果:
[ { "result": { "scientific": { "To be uploaded": 1 }, "contracts": { "To be reviewed": 1, "To be uploaded": 1 } } } ]
方案二:完整统计(显示所有存在的status,包括数量为0)
完全匹配期望输出,会自动补全所有已出现的status,即使某个key下该status数量为0:
db.collection.aggregate([ // 提取每个文档的key和status值 { $project: { key: 1, status: { $arrayElemAt: [ { $map: { input: { $filter: { input: "$custom_properties", cond: { $eq: ["$$this.key", "status"] } } }, as: "prop", in: "$$prop.value" } }, 0 ] } } }, // 收集所有唯一的status值,同时保留原始文档的key和status关联 { $group: { _id: null, allStatuses: { $addToSet: "$status" }, docs: { $push: { key: "$key", status: "$status" } } } }, // 为每个key生成包含所有status的统计项 { $project: { _id: 0, keyGroups: { $group: { input: "$docs", id: "$$this.key", accumulator: { $mergeObjects: { $arrayToObject: [ { $map: { input: "$allStatuses", as: "s", in: { k: "$$s", v: { $sum: { $cond: [ { $and: [{ $eq: ["$$this.key", "$$current.id"] }, { $eq: ["$$this.status", "$$s"] }] }, 1, 0 ] } } } } } ] } } } } } }, // 转换为最终的键值对结构 { $project: { result: { $arrayToObject: "$keyGroups" } } } ])
输出结果:
[ { "result": { "scientific": { "To be uploaded": 1, "To be reviewed": 0 }, "contracts": { "To be reviewed": 1, "To be uploaded": 1 } } } ]
内容的提问来源于stack exchange,提问作者papawheelie
相关产品推荐
相关产品推荐

