MongoDB聚合:深度嵌套数组过滤的优化与条件复用问题
MongoDB嵌套数组过滤的两个优化问题
这是《如何在投影阶段过滤嵌套数组?》的后续问题,需求是:先通过$match筛选出包含符合条件(person.empl.job.go为"Y")的深层数组的文档,再保留原文档结构仅过滤目标数组。目前已通过嵌套$map与$mergeObjects实现需求,示例代码及输出如下:
示例数据与聚合代码
// 插入测试数据 db.collection.insertMany([ { "person": [ { "name": "joe", "empl": [ { "name": "joe_e0", "job": [ { "id": "joe_e0_j0", "go": "Y" }, { "id": "joe_e0_j1", "go": "N" } ]}]}]}, { "person": [ { "name": "amy", "empl": [ { "name": "amy_e0", "job": [ { "id": "amy_e0_j0", "go": "Y" }, { "id": "amy_e0_j1", "go": "N" } ]}]}]}, { "person": [ { "name": "tom", "empl": [ { "name": "tom_e0", "job": [ { "id": "tom_e0_j0", "go": "N" }, { "id": "tom_e0_j1", "go": "N" }, { "id": "tom_e0_j2", "go": "N" } ]}]}]} ]); // 聚合查询 db.collection.aggregate([ { "$match": {"person.empl.job.go": "Y"} }, { $set: { person: { $map: { input: "$person", as: "p", in: { $mergeObjects: [ "$$p", { "empl": { $map: { input: "$$p.empl", as: "e", in: { $mergeObjects: [ "$$e", { "job": { $filter: { input: "$$e.job", as: "j", cond: { $eq: ["$$j.go","Y"] } } }}]}}} // empl }]}}} // person }} ]);
预期输出
[ { "_id": ObjectId("5a934e000102030405000000"), "person": [ { "empl": [ { "job": [ { "go": "Y", "id": "joe_e0_j0" }], "name": "joe_e0" }], "name": "joe" }]}, { "_id": ObjectId("5a934e000102030405000001"), "person": [ { "empl": [ { "job": [ { "go": "Y", "id": "amy_e0_j0" }], "name": "amy_e0"}], "name": "amy" }]} ]
问题1:是否必须交替使用$map和$mergeObjects来遍历嵌套结构并仅过滤目标数组,存在更简洁高效的实现方式吗?
不需要,MongoDB 4.2及以上版本支持数组通配符$[],可以直接定位到多层嵌套数组的目标字段,无需嵌套$map和$mergeObjects,写法更简洁且效率更高——直接定位目标数组进行过滤,避免了遍历外层数组并合并对象的额外开销。
优化后的聚合查询:
db.collection.aggregate([ { "$match": {"person.empl.job.go": "Y"} }, { $set: { // 用$[]通配符匹配所有person、empl数组元素,直接过滤job数组 "person.$[].empl.$[].job": { $filter: { input: "$$CURRENT", as: "j", cond: { $eq: ["$$j.go","Y"] } } } } } ]);
$[]表示匹配数组中的所有元素,通过person.$[].empl.$[].job可以直接访问所有层级下的job数组,再用$filter过滤符合条件的元素,完全保留原文档结构。
问题2:实际场景中person.empl.job的匹配条件较为复杂,需在$match和$filter中重复编写,能否避免这种重复?
可以通过两种方式避免重复编写复杂条件:
方案1:代码层面抽离条件变量
将复杂条件定义为独立变量,在$match和$filter中直接引用,这是最通用且高效的方案:
// 抽离复杂过滤条件(可添加任意多的原生表达式逻辑) const jobFilterCond = { $and: [ { $eq: ["$$j.go", "Y"] }, { $gte: ["$$j.salary", 5000] } ] }; // 聚合查询 db.collection.aggregate([ { $match: { $expr: { // 用$expr结合嵌套$map+$anyElementTrue判断是否存在符合条件的job $anyElementTrue: { $map: { input: "$person", as: "p", in: { $anyElementTrue: { $map: { input: "$$p.empl", as: "e", in: { $anyElementTrue: { $map: { input: "$$e.job", as: "j", in: jobFilterCond } } } } } } } } } } }, { $set: { "person.$[].empl.$[].job": { $filter: { input: "$$CURRENT", as: "j", cond: jobFilterCond } } } } ]);
方案2:使用自定义函数(MongoDB 4.4+)
如果条件逻辑包含复杂分支判断,可使用$function定义自定义函数,在$match和$filter中复用:
db.collection.aggregate([ { $match: { $expr: { $anyElementTrue: { $map: { input: "$person", as: "p", in: { $anyElementTrue: { $map: { input: "$$p.empl", as: "e", in: { $anyElementTrue: { $map: { input: "$$e.job", as: "j", in: { $function: { body: function(job) { // 编写复杂判断逻辑 return job.go === "Y" && job.salary >= 5000 && job.department === "tech"; }, args: ["$$j"], lang: "js" } } } } } } } } } } } } }, { $set: { "person.$[].empl.$[].job": { $filter: { input: "$$CURRENT", as: "j", cond: { $function: { body: function(job) { // 复用同一个判断逻辑 return job.go === "Y" && job.salary >= 5000 && job.department === "tech"; }, args: ["$$j"], lang: "js" } } } } } } ]);
注意:自定义函数性能略低于原生表达式,优先推荐方案1。
内容的提问来源于stack exchange,提问作者arielCo
相关产品推荐
相关产品推荐

