MongoDB Node环境下按WorkFlow与Service数组字段合并文档
MongoDB按WorkFlow和Service数组分组合并文档解决方案
需要按WorkFlow文本字段和Service数组字段(数组内元素顺序不影响分组)分组合并文档,将同组的原始文档内容整理到Assets数组中,同时合并去重Service字段。
方法一:使用MongoDB聚合管道(推荐)
利用聚合管道的阶段操作,先标准化Service数组的分组键,再完成分组和结构整理:
完整聚合代码
db.collection.aggregate([ // 1. 标准化Service数组:提取value并排序,生成用于分组的唯一key { $addFields: { serviceGroupKey: { $sortArray: { input: "$Service.value", sortBy: 1 } } } }, // 2. 按WorkFlow和标准化后的serviceGroupKey分组 { $group: { _id: { WorkFlow: "$WorkFlow", serviceGroupKey: "$serviceGroupKey" }, // 保留第一个文档的Service作为合并后的Service(同组内结构一致) Service: { $first: "$Service" }, // 收集同组的资产信息 Assets: { $push: { _id: "$_id", AssetServiceClassification: "$AssetServiceClassification", LevelArray: "$LevelArray" } } } }, // 3. 整理输出结构,移除临时分组key { $project: { _id: 0, WorkFlow: "$_id.WorkFlow", Service: 1, Assets: 1 } } ])
代码解释
$addFields阶段:将Service数组中的value提取出来并排序,生成serviceGroupKey,确保元素顺序不同但内容相同的Service数组会被归为同一组。$group阶段:以WorkFlow和serviceGroupKey作为分组依据,用$first保留一组中的Service结构,用$push收集所有同组的资产信息到Assets数组。$project阶段:调整输出结构,去除临时的_id分组字段,整理成期望的格式。
方法二:客户端代码处理(非聚合方式)
如果不想使用聚合管道,可以在查询所有文档后,通过客户端代码完成分组逻辑,以Node.js为例:
示例代码
async function groupDocuments() { const docs = await db.collection.find().toArray(); const groupMap = new Map(); docs.forEach(doc => { // 生成分组键:WorkFlow + 排序后的Service value字符串 const sortedServiceValues = doc.Service.map(s => s.value).sort().join(','); const groupKey = `${doc.WorkFlow}_${sortedServiceValues}`; if (!groupMap.has(groupKey)) { groupMap.set(groupKey, { WorkFlow: doc.WorkFlow, Service: doc.Service, Assets: [] }); } // 将当前文档的资产信息加入Assets数组 groupMap.get(groupKey).Assets.push({ _id: doc._id.toString(), AssetServiceClassification: doc.AssetServiceClassification, LevelArray: doc.LevelArray }); }); // 将Map转换为数组,即最终结果 return Array.from(groupMap.values()); }
代码说明
- 查询所有文档后,通过
sort()统一Service中value的顺序,生成唯一的分组键。 - 使用
Map存储分组,键为生成的分组标识,值为目标结构的对象。 - 遍历每个文档,将资产信息追加到对应分组的
Assets数组中,最后将Map转换为数组得到结果。
输入示例
[{ "_id": { "$oid": "62f9fd54259335683bc54ac3" }, "WorkFlow": "Vendor", "Service": [ { "value": "6235b52ea216f20e1b00ad43" }, { "value": "6235b538a216f20e1b00ad46" } ], "AssetServiceClassification": "Business", "LevelArray": [ { "Level": "6235b4f5a216f20e1b00ad36", "StaffName": "620a23bbc7e6a4378ff8ad74", "Designation": "61efa3a5a444008633b223dd" }, { "Level": "6235b500a216f20e1b00ad39", "StaffName": "620b4d4995c3061565e63b08", "Designation": "61efa3aca444008633b223e0" } ] }, { "_id": { "$oid": "62f9f5f9e26c8912b86c61b8" }, "WorkFlow": "Vendor", "Service": [ { "value": "6235b538a216f20e1b00ad46" }, { "value": "6235b52ea216f20e1b00ad43" } ], "AssetServiceClassification": "Normal", "LevelArray": [ { "Level": "6235b4f5a216f20e1b00ad36", "StaffName": "620a2351c7e6a4378ff8ad4c", "Designation": "61efa3a5a444008633b223dd" }, { "Level": "6235b500a216f20e1b00ad39", "StaffName": "620a2387c7e6a4378ff8ad60", "Designation": "61efa3aca444008633b223e0" } ] }]
输出示例
[{ "WorkFlow": "Vendor", "Service": [ { "value": "6235b52ea216f20e1b00ad43" }, { "value": "6235b538a216f20e1b00ad46" } ], "Assets": [ { "_id": "62f9fd54259335683bc54ac3", "AssetServiceClassification": "Business", "LevelArray": [ { "Level": "6235b4f5a216f20e1b00ad36", "StaffName": "620a23bbc7e6a4378ff8ad74", "Designation": "61efa3a5a444008633b223dd" }, { "Level": "6235b500a216f20e1b00ad39", "StaffName": "620b4d4995c3061565e63b08", "Designation": "61efa3aca444008633b223e0" } ] }, { "_id": "62f9f5f9e26c8912b86c61b8", "AssetServiceClassification": "Normal", "LevelArray": [ { "Level": "6235b4f5a216f20e1b00ad36", "StaffName": "620a2351c7e6a4378ff8ad4c", "Designation": "61efa3a5a444008633b223dd" }, { "Level": "6235b500a216f20e1b00ad39", "StaffName": "620a2387c7e6a4378ff8ad60", "Designation": "61efa3aca444008633b223e0" } ] } ] }]
内容的提问来源于stack exchange,提问作者Prasanth
相关产品推荐
相关产品推荐

