直接在$group的_id中组合多个字段即可实现多字段去重,这是$group默认行为;需计数则加$sum:1,查重复需后续$match:{count:{$gt:1}}。

直接在 $group 的 _id 中组合多个字段就能去重
只要把需要联合去重的字段写进 _id 对象里,MongoDB 就会按这个组合值自动分组,等效于“多字段去重”。这不是技巧,而是 $group 的默认行为:_id 值唯一,所以相同组合只保留一个分组文档。
常见错误是误以为必须用 $addToSet 才算去重——其实不用。只要不额外聚合字段(比如不加 $sum 或 $push),$group 本身已完成了去重动作。
-
_id: { a: "$fieldA", b: "$fieldB" }→ 按 fieldA 和 fieldB 联合去重 -
_id: { a: "$fieldA", b: "$fieldB", c: "$fieldC" }→ 三字段联合去重 - 字段顺序不影响去重结果,但影响输出结构(
$_id.a和$_id.b的键名由你定义)
去重后要计数,就在 $group 里加 $sum: 1
单纯分组只是“列出所有不重复组合”,如果要统计有多少个这样的唯一组合,必须显式加一个计数字段。最常用的是 count: { $sum: 1 },它对每个分组累加 1,最终得到该组合出现的原始文档数。
注意:$sum: 1 统计的是“该组合在原集合中出现了几次”,不是“去重后剩几个”。如果你只想知道去重后总共有多少种组合(即去重总数),那应该在最外层再套一层 $group,把 _id 设为 null,然后 $sum: 1。
- 查“每个 name+age 组合出现几次” →
{ $group: { _id: { name: "$name", age: "$age" }, count: { $sum: 1 } } } - 查“总共多少个不重复的 name+age 组合” → 在上一步结果后再接
{ $group: { _id: null, total: { $sum: 1 } } } - 别漏写
allowDiskUse: true,当组合数极多时,内存可能溢出
想返回干净字段名?必须用 $project 提升 _id 子字段
聚合结果里,组合字段默认藏在 _id.a、_id.b 下。如果你希望输出是 { "a": "...", "b": "...", "count": 2 } 这种平铺结构,就必须加 $project 阶段手动“提级”。
否则,很多客户端或 ORM(比如 Spring Data MongoDB)会把 _id 当作整个对象反序列化,导致 DTO 字段映射失败或为空——这是实际开发中最常踩的坑。
- 用
name: "$_id.name"把_id.name映射成顶层name - 记得加
_id: 0排除原始_id字段,避免干扰 - Spring 中若用
AggregationResults<mydto></mydto>,DTO 字段名必须和$project输出的 key 完全一致
去重计数和查重复记录是两回事,别混用
有人把“多字段去重计数”和“找出哪些多字段组合重复了”当成一回事,其实目标完全不同:
- 去重计数:关注“有多少种唯一组合”或“每种组合出现几次”,用
$group + $sum即可 - 查重复记录:关注“哪些组合出现 ≥2 次”,得在
$group后接$match: { count: { $gt: 1 } } - 查重复时若要返回原始文档,不能只靠
$group,得用$push: "$$ROOT"或$addToSet: "$$ROOT"保存原始数据
字段嵌套层级、是否保留原始文档、是否需要分页——这些细节一旦没对齐,结果就差很远。别假设“去重了自然就能看到重复项”。


















