MongoDB多层嵌套数组匹配聚合新增字段结果异常问题
问题背景
需要检测MongoDB文档内3个位置是否存在值为input的字段,只要存在任意一处匹配,就为文档新增值为"input"的字符串类型element字段。
数据库返回的文档共有3种典型结构:
三类文档结构示例
第一类
{ _id: 1234, informations: { ... }, data_fields: [ { name: "input", value: "loremipsum" }, { fieldset_name: "label", value: "loremipsum" } ] }
第二类
{ _id: 2451, informations: { ... }, data_fields: [ { name: "number", value: "loremipsum" }, { fieldset_name: "input", value: "loremipsum" }, { fieldset_name: "button", value: "loremipsum", fieldset_data: [ [ { "fieldset.name": "home", "fieldset.value": "loremipsum" } ] ] } ] }
第三类
{ _id: 4561, informations: { ... }, data_fields: [ { fieldset_name: "button", value: "loremipsum", fieldset_data: [ [ { "fieldset.name": "input", "fieldset.value": "loremipsum" }, { "fieldset.name": "label", "fieldset.value": "loremipsum" } ], [ { "fieldset.name": "home", "fieldset.value": "loremipsum" } ] ] } ] }
检测范围
data_fields数组下所有元素的name属性data_fields数组下所有元素的fieldset_name属性data_fields数组下元素嵌套的fieldset_data二维数组内,每个子项的fieldset.name属性
预期输出
匹配到input的文档新增element: "input"字段,示例:
{ _id: 1234, informations: { ... }, data_fields: [ { name: "input", value: "loremipsum" }, { fieldset_name: "label", value: "loremipsum" } ], element: "input" }
原有实现的问题
原有聚合管道使用多层$map做遍历,返回结果为包含null、嵌套空数组的结构,无法得到预期的字符串值:
{ $addFields: { element: { $map: { input: "$data_fields", as: "data_field", in: { $cond: { if: { $eq: ["$$data_field.name", "input"] }, then: "$$data_field.name", else: { $cond: { if: { $eq: ["$$data_field.fieldset_name", "input"] }, then: "$$data_field.fieldset_name", else: { $map: { input: "$$data_field.fieldset_data", as: "fieldsets", in : { $map: { input: "$$fieldsets", as: "fieldset", in: { $cond: { if: { $eq: ["$$fieldset.name", "input"] }, then: "$$fieldset.name", else: "$$REMOVE" }} }} }} }} }} }} }}
实际返回错误示例:
{ _id: 1234, informations: { ... }, data_fields: [ { name: "input", value: "loremipsum" }, { fieldset_name: "label", value: "loremipsum" } ], element: [null, "input", [null, null, null], null] }
修正方案
原有代码存在3个核心错误:
$map返回结果与输入数组长度完全一致,不匹配的位置会默认返回null,无法直接得到单值结果- 嵌套遍历
fieldset_data时没有做扁平化处理,也没有过滤空值,最终生成多层嵌套结构 - 注意:嵌套字段的键名是带点的字符串
fieldset.name,不是嵌套对象结构,原有代码写$$fieldset.name会被MongoDB解析为取fieldset对象下的name属性,和实际字段名不匹配。
使用以下聚合管道即可实现需求:
[ { $addFields: { element: { $reduce: { input: "$data_fields", initialValue: [], in: { $concatArrays: [ "$$value", // 匹配当前项name字段 { $cond: [{ $eq: ["$$this.name", "input"] }, ["input"], []] }, // 匹配当前项fieldset_name字段 { $cond: [{ $eq: ["$$this.fieldset_name", "input"] }, ["input"], []] }, // 遍历嵌套的fieldset_data二维数组 { $reduce: { input: { $ifNull: ["$$this.fieldset_data", []] }, initialValue: [], in: { $concatArrays: [ "$$value", { $reduce: { input: "$$this", initialValue: [], in: { $concatArrays: [ "$$value", // 匹配嵌套项的fieldset.name字段 { $cond: [{ $eq: ["$$this.fieldset.name", "input"] }, ["input"], []] } ] } } } ] } } } ] } } } } }, { $addFields: { element: { $cond: [ { $gt: [{ $size: "$element" }, 0] }, { $arrayElemAt: ["$element", 0] }, "$$REMOVE" ] } } } ]
逻辑说明
- 全程使用
$reduce做数组遍历,通过$concatArrays拼接匹配结果,不匹配的位置直接拼接空数组,不会产生null值 - 对可能不存在的
fieldset_data字段用$ifNull兜底为空数组,避免遍历不存在字段时报错 - 二维
fieldset_data使用双层$reduce遍历,所有层级的匹配结果都会被扁平化为一维数组 - 最后判断匹配数组长度,存在匹配项时取第一个值(所有匹配值均为
input,取任意项即可)作为字符串类型的element值,无匹配则不生成该字段,完全符合预期。
内容的提问来源于stack exchange,提问作者Eviane
相关产品推荐
相关产品推荐

