You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB $graphLookup如何获取重复指向的两条子产品记录?

问题描述

我在MongoDB中存储产品及其子产品,子产品不是子文档,而是集合里的独立文档。Schema定义如下:

const productTypeSchema = mongoose.Schema({ 
  name: { type: String, required: true },
  category: { type: String, required: true },  
  subProducts: [subProductsSchema],
})
const subProductsSchema = mongoose.Schema({  
  name: { type: String, required: true },  
  subProductId: {    
    type: mongoose.Schema.Types.ObjectId,    
    ref: "product",  
  },
});

示例数据:

{ 
  _id: 1, 
  name: 'Steel Grid', 
  Category: 'Finished Product',     
  subProducts: [
    { _id: 6, name: 'Load Bar', subProductId: 2},                   
    { _id: 7, name: 'Cross Bar', subProductId: 2}
  ]
},
{ 
  _id: 2, 
  name: 'Steel Bar', 
  Category: 'Raw Material'
}

用$graphLookup查询产品和所有子产品时:

$graphLookup: {          
  from: "product",          
  startWith: "$_id",          
  connectFromField: "subProducts.subProductId",          
  connectToField: "_id",           
  depthField: "subpartlevel",          
  as: "subParts",        
},

因为两个子产品指向同一个文档,$graphLookup只返回一条记录,怎么才能拿到原数组里的两条子产品记录?

解决方案

$graphLookup本身就是返回匹配到的唯一文档,不管原数组里有多少个条目指向它,都会自动去重。要保留原subProducts里的所有条目,同时关联对应产品数据,得换个聚合思路:

  • 拆分子产品数组:用$unwind把subProducts数组拆成单个文档,这样每个原条目都能单独处理。
  • 关联产品详情:用$lookup根据subProductId从product集合拉取对应产品的完整数据。
  • 重组原结构:把关联后的子产品条目重新聚合回原产品文档,确保所有原条目都被保留。

如果需要递归获取多层级的子产品,可以在关联后的产品数据上重复这套逻辑。

聚合代码示例
db.product.aggregate([
  // 可选:只查询目标产品,比如_id为1的Steel Grid
  { $match: { _id: 1 } },
  // 展开subProducts数组,每个子条目成为独立文档
  { $unwind: "$subProducts" },
  // 关联对应产品的详情
  {
    $lookup: {
      from: "product",
      localField: "subProducts.subProductId",
      foreignField: "_id",
      as: "subProducts.productDetail"
    }
  },
  // 把productDetail数组转成单个对象(因为每个subProductId只匹配一个产品)
  { $addFields: { "subProducts.productDetail": { $arrayElemAt: ["$subProducts.productDetail", 0] } } },
  // 重新聚合回原产品结构,保留所有子产品条目
  {
    $group: {
      _id: "$_id",
      name: { $first: "$name" },
      category: { $first: "$category" },
      subProducts: { $push: "$subProducts" }
    }
  },
  // 可选:递归获取子产品的子产品(如果需要多层级)
  {
    $addFields: {
      "subProducts": {
        $map: {
          input: "$subProducts",
          as: "subItem",
          in: {
            _id: "$$subItem._id",
            name: "$$subItem.name",
            subProductId: "$$subItem.subProductId",
            productDetail: "$$subItem.productDetail",
            // 对子产品的subProducts重复上述逻辑,获取下一级子产品
            subParts: {
              $cond: {
                if: { $ne: ["$$subItem.productDetail.subProducts", undefined] },
                then: {
                  $let: {
                    vars: { parentProduct: "$$subItem.productDetail" },
                    in: {
                      $map: {
                        input: "$$parentProduct.subProducts",
                        as: "subSubItem",
                        in: {
                          $mergeObjects: [
                            "$$subSubItem",
                            {
                              productDetail: {
                                $arrayElemAt: [
                                  { $filter: { input: "$$parentProduct.subProducts", cond: { $eq: ["$$this.subProductId", "$$subSubItem.subProductId"] } } },
                                  0
                                ]
                              }
                            }
                          ]
                        }
                      }
                    }
                  }
                },
                else: []
              }
            }
          }
        }
      }
    }
  }
])
结果说明

运行这个聚合后,原subProducts里的两个条目(Load Bar和Cross Bar)都会被保留,每个条目都会带上对应的productDetail(也就是Steel Bar的完整数据),不会出现去重的情况。如果需要更深层级的子产品,可以继续扩展嵌套的聚合逻辑。

内容的提问来源于stack exchange,提问作者Mahesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 13:20:27