MongoDB嵌套数组指定对象的值获取:寻求更简洁高效实现方案
更简洁高效的MongoDB查询方案:获取嵌套数组中的指定值
请看以下示例数据。V1、V2和V3是values数组中对象的唯一标识符,这些标识符只会使用一次(即集合中即使属于不同数组的values对象,也不会有相同的id)。
db.experiment.deleteMany({}); db.experiment.insertMany([ { 'id': 'Q1', 'statements': [ { 'property': 'P1', 'values': [ {'id': 'V1', 'value': 'Q1 foo'}, {'id': 'V2', 'value': 'Q1 bar'} ] }, { 'property': 'P2', 'values': [ {'id': 'V3', 'value': 'Q1 baz'}, {'id': 'V4', 'value': 'Q1 qux'} ] } ] }, { 'id': 'Q2', 'statements': [ { 'property': 'P1', 'values': [ {'id': 'V5', 'value': 'Q2 foo'}, {'id': 'V6', 'value': 'Q2 bar'} ] }, { 'property': 'P2', 'values': [ {'id': 'V7', 'value': 'Q2 baz'}, {'id': 'V8', 'value': 'Q2 qux'} ] } ] } ]);
需求与预期结果
需要获取顶层id为Q1的文档中,id为V3的对象的value字段,预期结果如下:
{ _id: ObjectId(<object-id>), value: 'Q1 baz' }
原始实现方案
你已通过以下聚合管道得到了预期结果:
printjson(db.experiment.aggregate([ { '$match': { 'id': 'Q1' } }, // I used $unwind because I did not know how to use $filter in an // array that is inside another array. { '$unwind': '$statements' }, // I used $project in order to use $filter { '$project': { 'projectResult': { $filter: { input: '$statements.values', as: 'value', cond: { $eq: ["$$value.id", "V3"] } } } } }, // I used $match to filter out empty arrays. { '$match': { 'projectResult': {$ne: []} } }, // At this point, the key projectResult is an array, so I needed to // unwind it in order to $project the key "value" { '$unwind': '$projectResult' }, { '$project': { 'value': '$projectResult.value' } } ]))
执行结果:
[ { _id: ObjectId('66e0a3f4370082960c2202d8'), value: 'Q1 baz' } ]
优化后的实现方案
方法一:聚合管道简化版(减少阶段,避免多次$unwind)
利用$reduce遍历外层数组,结合$filter筛选内层数组,再通过$arrayElemAt和$replaceRoot直接提取目标值,无需多次展开数组:
printjson(db.experiment.aggregate([ { $match: { id: "Q1" } }, { $project: { targetValue: { $arrayElemAt: [ { $reduce: { input: "$statements", initialValue: [], in: { $concatArrays: [ "$$value", { $filter: { input: "$$this.values", as: "v", cond: { $eq: ["$$v.id", "V3"] } } } ] } } }, 0 ] } } }, { $replaceRoot: { newRoot: { _id: "$_id", value: "$targetValue.value" } } }, { $match: { value: { $exists: true } } } ]))
方法二:find查询配合投影(更简洁的写法)
直接用find定位文档,通过投影中的数组运算符提取目标值,最后用map整理结果格式:
printjson(db.experiment.find( { id: "Q1", "statements.values.id": "V3" }, { _id: 1, targetValue: { $arrayElemAt: [ { $reduce: { input: "$statements", initialValue: [], in: { $concatArrays: [ "$$value", { $filter: { input: "$$this.values", as: "v", cond: { $eq: ["$$v.id", "V3"] } } } ] } } }, 0 ] } } ).map(doc => ({ _id: doc._id, value: doc.targetValue.value })))
方法三:MongoDB 4.4+版本简化方案
利用$first和$getField进一步精简代码:
printjson(db.experiment.aggregate([ { $match: { id: "Q1", "statements.values.id": "V3" } }, { $project: { value: { $getField: { field: "value", input: { $first: { $reduce: { input: "$statements", initialValue: [], in: { $concatArrays: ["$$value", { $filter: { input: "$$this.values", as: "v", cond: { $eq: ["$$v.id", "V3"] } } }] } } } } } } } } ]))
优化优势
这些方案避免了多次$unwind和$match操作:
$unwind会将数组展开为多个文档,增加后续处理的文档数量,降低效率- 用
$reduce+$filter可以在单个阶段完成嵌套数组的筛选,减少管道阶段,提升查询性能 - 代码更简洁,逻辑更集中
内容的提问来源于stack exchange,提问作者Rodrigo Morales
相关产品推荐
相关产品推荐

