MongoDB聚合:未知邮箱字段名分组及unwind后完整文档数据获取求助
解决按未知邮箱字段分组并保留完整文档的问题
你的核心问题在于,原来的聚合流程拆分文档为键值对数组后,丢失了原始文档的引用——你只把拆分后的单个键值对推入了dups,而非完整的原始文档。下面是修正后的聚合管道,完美匹配你的需求:
db.test.aggregate([ // 第一步:保留完整原始文档,同时把文档转为键值对数组 { $project: { originalDoc: "$$ROOT", data: { $objectToArray: "$$ROOT" } } }, // 第二步:拆分键值对数组,让每个键值对单独成为一条记录 { $unwind: "$data" }, // 第三步:过滤出值符合邮箱格式的键值对,只保留邮箱相关的字段 { $match: { "data.v": /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/ } }, // 第四步:按邮箱值分组,把完整原始文档推入dups,并统计分组数量 { $group: { _id: "$data.v", // 直接用邮箱值作为分组ID dups: { $push: "$originalDoc" }, count: { $sum: 1 } } }, // 可选:如果需要把邮箱字段名也展示出来,可以添加这个阶段 { $addFields: { emailField: { $arrayElemAt: ["$dups.data.k", 0] } } } ])
关键修正逻辑说明:
- 保留原始文档:第一个
$project阶段用originalDoc: "$$ROOT"把整个原始文档保存下来,这是后续能在dups中拿到完整文档的核心。 - 精准过滤邮箱字段:在
$match阶段直接筛选值符合邮箱正则的键值对,避免无关字段干扰分组逻辑。 - 分组时引用原始文档:
$group阶段的dups使用$push: "$originalDoc",确保每个分组里的dups都是完整的原始文档,而非单个键值对。
最终输出效果(完全匹配你的期望):
[ { "_id": "abc@gmail.com", "count": 2, "dups": [ { "_id": "62af2c14e50225b659ab68a4", "name": "pqr", "email": "abc@gmail.com", "phone": "9876543210" }, { "_id": "62af2c14e50225b659ab68a6", "name": "abc", "email": "abc@gmail.com", "phone": "4567890123" } ] }, { "_id": "xyz@gmail.in", "count": 2, "dups": [ { "_id": "62af2c14e50225b659ab68a3", "name": "stuv", "email": "xyz@gmail.in", "phone": "12345678990" }, { "_id": "62af2c14e50225b659ab68a5", "name": "xyz", "email": "xyz@gmail.in", "phone": "2345678901" } ] } ]
额外优化建议:
- 如果你的数据库中存在多个邮箱字段(比如同时有
gmail和personalMail),这个管道会把同一个文档按不同邮箱字段分别分组。如果需要避免重复统计同一文档,可以在$match后添加一个$group阶段,按originalDoc._id去重。 - 邮箱正则可以优化为更通用的版本,比如支持
.co.uk这类多级域名:/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/。
内容的提问来源于stack exchange,提问作者anu




