MongoDB聚合查询如何结合$group与$project实现关联去重查询
问题根因
- 原聚合语句存在基础语法错误:第二个
$lookup阶段的localField字段值缺少闭合引号,且关联逻辑不符合原SQL要求:原SQL中service、property表均通过service_id = p.service_id关联,原写法错误使用producer_id关联property_id,关联结果本身就不对。 - 字段丢失的核心原因是MongoDB聚合为管道执行模型,只有上一阶段输出的字段能流转到下一阶段。原写法
$group阶段仅将service_id、property_id设为分组_id,producer表的name、service表的value、property表的property等字段均未被保留到分组输出,后续$project阶段自然无法读取到对应值。 - 原写法执行逻辑存在性能问题:先对两个关联表做全量
$lookup再$unwind会产生大量冗余笛卡尔积数据,后续全局分组的计算量会随数据量上涨指数级提升,把所有字段加入分组键确实会进一步降低性能,不是最优解。
优化实现方案
核心思路是将子表去重逻辑下推到$lookup子管道内执行,关联阶段就完成去重过滤,避免产生冗余数据,也不需要后续全局分组导致字段丢失,完全对齐原SQL的执行逻辑:
db.producer.aggregate([ // 关联service集合,子管道内完成过滤+去重 { $lookup: { from: "service", let: { pId: "$producer_id" }, pipeline: [ // 匹配service_id = producer.producer_id的记录 { $match: { $expr: { $eq: ["$service_id", "$$pId"] } } }, // 按service_id+value去重,对齐原SQL distinct逻辑 { $group: { _id: { service_id: "$service_id", value: "$value" } }}, // 把分组结果提升为普通文档结构 { $replaceRoot: { newRoot: "$_id" } } ], as: "ps" } }, // 关联property集合,子管道内完成过滤+去重 { $lookup: { from: "property", let: { pId: "$producer_id" }, pipeline: [ // 匹配service_id = producer.producer_id的记录 { $match: { $expr: { $eq: ["$service_id", "$$pId"] } } }, // 按property_id+property去重 { $group: { _id: { property_id: "$property_id", property: "$property" } }}, { $replaceRoot: { newRoot: "$_id" } } ], as: "pp" } }, // 依次展开两个关联数组,得到组合后的结果 { $unwind: "$ps" }, { $unwind: "$pp" }, // 直接投影需要的字段,无需额外全局分组 { $project: { producer_id: 1, name: 1, service_id: "$ps.service_id", value: "$ps.value", property_id: "$pp.property_id", property: "$pp.property", _id: 0 } } ])
方案优势
- 性能更高:去重逻辑在关联阶段就完成,不会产生多余的笛卡尔积数据,也不需要对全量关联结果做全局分组,数据量越大性能优势越明显。
- 无字段丢失问题:所有需要的字段在管道流转过程中全程保留,不需要把所有字段加入分组键,
$project阶段可以直接读取返回。 - 逻辑对齐:执行逻辑和原SQL完全一致,先过滤子表、对子表去重,再和主表关联,返回结果和原SQL执行结果匹配。基于你给出的样例数据,执行后会返回如下结果:
[ { producer_id: 1, name: 'test', service_id: 1, value: 12, property_id: 1, property: 12 }, { producer_id: 1, name: 'test', service_id: 1, value: 12, property_id: 1, property: 56 }, { producer_id: 1, name: 'test', service_id: 1, value: 13, property_id: 1, property: 12 }, { producer_id: 1, name: 'test', service_id: 1, value: 13, property_id: 1, property: 56 }, { producer_id: 2, name: 'test2', service_id: 2, value: 14, property_id: 2, property: 34 } ]
注:原SQL语句末尾缺少和property子查询的关联条件,上述实现是基于你给出的集合样例数据、字段对应关系补全关联逻辑后的版本,如果实际关联字段有调整,修改
$lookup内的$match条件即可。
内容的提问来源于stack exchange,提问作者Neel
相关产品推荐
相关产品推荐

