如何用MongoDB聚合管道实现键值互换并整合邮箱与手机号
如何通过MongoDB聚合直接生成键为邮箱/手机号的结果
集合文档结构
现有MongoDB集合包含以下文档:
{ "_id" : ObjectId("65977bef1ccf990fe33ffc0d"), "email" : "yww24@gmail.com" } { "_id" : ObjectId("65977bef1ccf990fe33ffc0f"), "email" : "iqiyz@gmail.com", "phone" : "8887774455" } { "_id" : ObjectId("65977bef1ccf990fe33ffc10"), "email" : "46d9s@gmail.com" } { "_id" : ObjectId("65977bef1ccf990fe33ffc0e"), "phone" : "6669997744" }
期望结果
希望通过聚合操作直接得到以邮箱、手机号为键的对象,值可以是任意标识(如序号、true等),示例如下:
{ "yww24@gmail.com": ["0"], "iqiyz@gmail.com": ["1"], "46d9s@gmail.com": ["2"], "8887774455": ["1"], "6669997744": ["3"] }
现有前端实现(Lodash方式)
当前可通过查询后用Lodash处理得到结果,但希望直接在数据库端完成聚合:
Coll.aggregate([ { $match: {} }, { $project: { _id: false, email: true, phone: true } } ]) .then(logs => { const result = { emails: _.invertBy(logs, 'email'), phones: _.invertBy(logs, 'phone') }; delete result.emails.undefined; delete result.phones.undefined; console.log({...result.emails, ...result.phones}); })
MongoDB聚合解决方案
以下聚合管道可直接在数据库端生成目标格式:
db.collection.aggregate([ // 给每个文档分配递增序号 { $setWindowFields: { sortBy: { _id: 1 }, output: { docIndex: { $documentNumber: {} } } } }, // 将email/phone转为统一的键值结构,过滤空值 { $project: { entries: { $filter: { input: [ { k: "$email", v: [ { $toString: "$docIndex" } ] }, { k: "$phone", v: [ { $toString: "$docIndex" } ] } ], cond: { $ne: ["$$this.k", null] } } } } }, // 拆分数组为单个文档 { $unwind: "$entries" }, // 收集所有键值对 { $group: { _id: null, allEntries: { $push: "$entries" } } }, // 将数组转为目标对象 { $replaceRoot: { newRoot: { $arrayToObject: "$allEntries" } } } ])
管道说明
- $setWindowFields:按
_id排序为每个文档分配唯一递增序号,作为值的来源; - $project:把
email和phone转为{k: 键名, v: [序号字符串]}的结构,用$filter过滤掉不存在的字段(值为null的情况); - $unwind:将每个文档的
entries数组拆分为单独文档,方便后续聚合; - $group:把所有键值对收集到一个数组中;
- $replaceRoot:通过
$arrayToObject将数组转换为目标格式的对象,替换根文档。
执行后将直接输出符合要求的结果,无需前端额外处理。
内容的提问来源于stack exchange,提问作者codemonkey
相关产品推荐
相关产品推荐

