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
相关产品推荐
相关产品推荐

