如何用MongoDB聚合构建依赖关系的嵌套层级结构?
MongoDB 使用$graphLookup构建自上而下的依赖层级结构
需要在MongoDB的relations集合中,针对指定文档生成完整的自上而下依赖层级。集合中文档的dependentOn字段为Object类型,内部按分类(如metrics、measures)存储依赖的_id数组;终止节点无dependentOn字段,仅包含反向关联的usedBy字段。
示例文档
// Doc 1: { "_id": "p_commerce_metrics_dtst_v1", "category": "dataset", "dependentOn": { "metrics": [ "net_booking_count_v1" ] } } // Doc 2: { "_id": "net_booking_count_v1", "category": "metric", "dependentOn": { "metrics": [ "cancelled_booking_count_v1", "gross_booking_count_v1" ] } } // Doc 3: { "_id": "cancelled_booking_count_v1", "category": "metric", "dependentOn": { "measures": [ "hb_cancel_measure_v1" ] } } // Doc 4: { "_id": "gross_booking_count_v1", "category": "metric", "dependentOn": { "measures": [ "hb_booking_measure_v1" ] } } // Doc 5(无依赖,终止节点): { "_id": "hb_cancel_measure_v1", "category": "measure", "usedBy": { "metrics": [ "cancelled_booking_count_v1", "more_metrics" ] } } // Doc 6(无依赖,终止节点): { "_id": "hb_booking_measure_v1", "category": "measure", "usedBy": { "metrics": [ "gross_booking_count_v1", "more_metrics" ] } }
期望输出
获取p_commerce_metrics_dtst_v1的完整依赖层级:
[ { "name_version": "p_commerce_metrics_dtst_v1", "category": "dataset", "dependent_on": { "metrics": [ "net_booking_count_v1", "cancelled_booking_count_v1", "gross_booking_count_v1" ], "measures": [ "hb_cancel_measure_v1", "hb_booking_measure_v1" ] } } ]
尝试的无效查询
[ { $match: { _id: "p_commerce_metrics_dtst_v1" }}, { $graphLookup: { from: "relations", startWith: "$dependentOn", connectFromField: "dependentOn", connectToField: "_id", depthField: "depth", as: "dependentOn" }} ]
正确的聚合实现方案
原查询的问题在于:$graphLookup的startWith和connectFromField需要传入可匹配_id的单一值或数组,而非Object类型的dependentOn。以下是修正后的聚合流程:
[ // 匹配目标文档 { $match: { _id: "p_commerce_metrics_dtst_v1" } }, // 提取初始依赖的所有_id数组,同时保留原文档字段 { $project: { category: 1, initialDependencies: { $reduce: { input: { $objectToArray: "$dependentOn" }, initialValue: [], in: { $concatArrays: ["$$value", "$$this.v"] } } }, initialDepsByType: "$dependentOn" } }, // 递归查询所有依赖节点 { $graphLookup: { from: "relations", startWith: "$initialDependencies", connectFromField: { $reduce: { input: { $ifNull: [{ $objectToArray: "$dependentOn" }, []] }, initialValue: [], in: { $concatArrays: ["$$value", "$$this.v"] } } }, connectToField: "_id", as: "allDependencies" } }, // 合并所有依赖的分类信息,并整理结构 { $project: { name_version: "$_id", category: 1, dependent_on: { $mergeObjects: [ "$initialDepsByType", { $arrayToObject: { $map: { input: "$allDependencies", as: "dep", in: { $arrayToObject: { $map: { input: { $ifNull: [{ $objectToArray: "$$dep.dependentOn" }, []] }, as: "type", in: { k: "$$type.k", v: "$$type.v" } } } } } } } ] } } }, // 对每个分类的数组去重并合并 { $set: { "dependent_on": { $arrayToObject: { $map: { input: { $objectToArray: "$dependent_on" }, as: "entry", in: { k: "$$entry.k", v: { $reduce: { input: "$$entry.v", initialValue: [], in: { $setUnion: ["$$value", "$$this"] } } } } } } } } }, // 移除多余字段 { $unset: ["_id"] } ]
关键步骤说明
- 提取初始依赖:用
$objectToArray将dependentOn转为键值对数组,再通过$reduce合并所有分类的依赖ID,作为$graphLookup的起始查询值。 - 递归查询依赖:
connectFromField动态提取每个节点的dependentOn中的所有ID,实现层级递归查询。 - 合并分类结构:用
$mergeObjects合并初始依赖和递归查询到的依赖,再通过$arrayToObject和$setUnion整理去重,生成按分类分组的依赖列表。 - 字段重命名与清理:将
_id改为name_version,移除多余字段,匹配期望输出格式。
内容的提问来源于stack exchange,提问作者Prabhjot
相关产品推荐
相关产品推荐

