MongoDB如何对双层嵌套对象数组执行$lookup关联查询
MongoDB聚合查询:关联嵌套数组并保留自定义字段
我有一个数据模型,每个product包含多个variant,每个variant又包含多个modification。数据库中的数据结构如下:
const mods = db.modifications.insertMany([ { title: 'Modification #1', image: 'img1.png', }, { title: 'Modification #2', image: 'img2.png', }, { title: 'Modification #3', image: 'img3.png', }, ]) db.products.insertOne({ slug: 'product1', title: 'Product #1', variants: [ { size: 20, price: 200, modifications: [ { id: mods.insertedIds[0], price: 10 }, { id: mods.insertedIds[1], price: 15 }, ], }, { size: 30, price: 250, modifications: [ { id: mods.insertedIds[0], price: 15 }, { id: mods.insertedIds[2], price: 20 }, ], }, ], })
需要执行聚合查询,将variants.modifications中的id关联到modifications集合的完整数据,同时保留每个modification对应的price字段,最终得到如下格式的结果:
{ slug: 'product1', title: 'Product #1', variants: [ { size: 20, price: 200, modifications: [ { _id: '…', title: 'Modification #1', image: '…', price: 10 }, { _id: '…', title: 'Modification #2', image: '…', price: 15 }, ], }, { size: 30, price: 250, modifications: [ { _id: '…', title: 'Modification #1', image: '…', price: 15 }, { _id: '…', title: 'Modification #3', image: '…', price: 20 }, ], }, ], }
我曾尝试先执行两次$unwind,再执行$lookup:
db.products.aggregate([ { $match: { slug: 'product1' } }, { $unwind: '$variants' }, { $unwind: '$variants.modifications' }, { $lookup: { from: 'modifications', localField: 'variants.modifications.id', foreignField: '_id', let: { price: '$variants.modifications.price' }, pipeline: [{ $addFields: { price: '$$price' } }], as: 'modifications', }, }, ])
但不知道如何通过$group将数据重新组合成目标格式。
解决方案
可以通过嵌套的$lookup结合$map和$mergeObjects实现,无需多次$unwind和$group,步骤如下:
- 使用
$lookup关联modifications集合,获取所有相关的修改项数据 - 遍历
variants数组,对每个variant的modifications数组进行映射,将id匹配到对应的修改项,并合并自定义的price字段
完整聚合查询代码:
db.products.aggregate([ { $match: { slug: 'product1' } }, // 关联modifications集合,获取所有相关的修改数据 { $lookup: { from: 'modifications', localField: 'variants.modifications.id', foreignField: '_id', as: 'modsData' } }, // 处理variants数组,将每个modification关联到完整数据并保留price { $addFields: { variants: { $map: { input: '$variants', as: 'variant', in: { // 保留variant的原有字段 size: '$$variant.size', price: '$$variant.price', modifications: { $map: { input: '$$variant.modifications', as: 'mod', in: { // 匹配对应的modsData项,合并price字段 $mergeObjects: [ { $first: { $filter: { input: '$modsData', cond: { $eq: ['$$this._id', '$$mod.id'] } }}}, { price: '$$mod.price' } ] } } } } } }, // 移除临时的modsData字段 modsData: '$$REMOVE' } } ])
代码解释
$lookup阶段:一次性关联所有与当前产品相关的修改项数据,存入modsData临时字段,避免多次关联的性能损耗。- 外层
$map:遍历每个variant,保留原有size和price字段,同时处理内部的modifications数组。 - 内层
$map:遍历每个modification,通过$filter从modsData中匹配对应的完整修改项,再用$mergeObjects将自定义的price字段合并进去,覆盖(或补充)原有数据。 $$REMOVE:清理临时的modsData字段,保证最终输出结构整洁。
内容的提问来源于stack exchange,提问作者crabvk
相关产品推荐
相关产品推荐

