MongoDB 4.4多层嵌套聚合查询报错,求正确实现方案
MongoDB 4.4 嵌套关联聚合查询问题解决
问题背景
使用MongoDB 4.4,需要编写聚合查询,根据_id查询offer文档,并返回包含嵌套的modules、每个module对应的items、每个item对应的notes的结构。
各集合结构如下:
// offers集合 { _id: '64a6c6ed3f24ff001b74cf9d', name: 'offer name', } // modules集合 { _id: '64a6c6ed3f24ff001b74cfab', name: 'module name', offerId: '64a6c6ed3f24ff001b74cf9d', } // items集合 { _id: '5e984aea5e6df000399e6b97', name: 'item name', moduleId: '64a6c6ed3f24ff001b74cfab', } // notes集合 { _id: '64c78caf0e5791001b15833c', name: 'note name', itemId: '5e984aea5e6df000399e6b97', }
期望返回结构:
{ _id: "64a6c6ed3f24ff001b74cf9d", name: "offer name", modules: [ { _id: "64a6c6ed3f24ff001b74cfab", name: "module name", items: [ { _id: "5e984aea5e6df000399e6b97", name: "item name", notes: [ { _id: "64c78caf0e5791001b15833c", name: "note name" }, ... ] }, ... ] }, ... ] }
编写的聚合查询执行时出现错误:Unrecognized expression '$push',查询代码如下:
db.getCollection("offers").aggregate([ { $match: { _id: ObjectId('64a6c6ed3f24ff001b74cf9d'), } }, { $lookup: { from: 'modules', localField: '_id', foreignField: 'offerId', as: 'modules', }, }, { $unwind: '$modules', }, { $lookup: { from: 'items', localField: 'modules._id', foreignField: 'moduleId', as: 'items', }, }, { $unwind: '$items', }, { $lookup: { from: 'notes', localField: 'items._id', foreignField: 'itemId', as: 'notes', }, }, { $group: { _id: "$_id", name: { $first: "$name" }, modules: { $push: { _id: "$modules._id", name: "$modules.name", items: { $push: { _id: "$items._id", name: "$items.name", notes: { $push: { _id: "$notes._id", name: "$notes.name", } }, } }, } } } }, { $project: { _id: 1, name: 1, modules: 1, } }, ]);
错误原因
MongoDB的$group阶段不支持嵌套使用$push操作符。$push只能作为$group中字段的直接聚合表达式,无法在$push生成的对象内部再次使用$push来嵌套构建数组结构。
解决方案:使用嵌套管道式$lookup
MongoDB 3.6及以上版本支持带自定义管道的$lookup,可以在关联集合时直接嵌套后续的关联逻辑,无需多次$unwind和$group,更高效且能直接生成预期的嵌套结构。
正确的聚合查询代码如下:
db.getCollection("offers").aggregate([ // 筛选目标offer { $match: { _id: ObjectId('64a6c6ed3f24ff001b74cf9d') } }, // 关联modules集合,并在modules的管道内嵌套关联items和notes { $lookup: { from: 'modules', let: { offerId: '$_id' }, pipeline: [ { $match: { $expr: { $eq: ['$offerId', '$$offerId'] } } }, // 在modules管道内关联items集合 { $lookup: { from: 'items', let: { moduleId: '$_id' }, pipeline: [ { $match: { $expr: { $eq: ['$moduleId', '$$moduleId'] } } }, // 在items管道内关联notes集合 { $lookup: { from: 'notes', localField: '_id', foreignField: 'itemId', as: 'notes' } }, // 可选:只保留items需要的字段 { $project: { _id: 1, name: 1, notes: 1 } } ], as: 'items' } }, // 可选:只保留modules需要的字段 { $project: { _id: 1, name: 1, items: 1 } } ], as: 'modules' } }, // 可选:保留offer需要的字段 { $project: { _id: 1, name: 1, modules: 1 } } ]);
代码解释
- 外层
$match:精准筛选目标offer,减少后续关联的数据量。 - 第一层
$lookup(关联modules):通过let定义变量传递offer的_id,用pipeline内的$match匹配关联的modules。 - 第二层
$lookup(关联items):在modules的管道内,继续用变量传递module的_id,关联对应的items。 - 第三层
$lookup(关联notes):在items的管道内,直接通过localField和foreignField关联对应的notes,生成嵌套数组。 $project阶段:可选,过滤掉不需要的字段,精简返回结果。
这种方式避免了多次$unwind和$group操作,逻辑清晰且性能更优,能直接生成符合预期的嵌套结构。
内容的提问来源于stack exchange,提问作者skumy
相关产品推荐
相关产品推荐

