如何更新含点的嵌套键?MongoDB查询与更新问题咨询
MongoDB含点键的查询与更新问题
原文档结构
[ { a: "", b: 1, "createdAccounts": { "test@gmail.com": { "id": ["a", "b", "c"] } } } ]
现有查询背景
因邮箱键包含点(.),常规findOne的点路径匹配会将邮箱里的点解析为嵌套层级,导致无法命中目标键。当前使用聚合查询实现匹配:
const email = "test@gmail.com" let account = await db.collection("users").aggregate( [ { "$project": { "createdAccounts": { "$objectToArray": "$createdAccounts" } } }, { "$match": { "createdAccounts.k": email } }, { "$project": { "createdAccounts": { "$arrayToObject": "$createdAccounts" } } } ]).toArray().then(results => { const document = results[0]; if (document && document.createdAccounts) return document.createdAccounts[email]; return null; })
1. 更优的查询方式
可以简化聚合逻辑,或直接用findOne结合$expr实现更高效的查询,无需多次转换对象与数组:
方式一:简化聚合查询
直接在$match中用$expr过滤,最后提取目标账户,省去一次$project转换:
const email = "test@gmail.com"; let account = await db.collection("users").aggregate([ { $match: { $expr: { // 检查目标邮箱是否存在于createdAccounts的键集合中 $in: [email, { $map: { input: { $objectToArray: "$createdAccounts" }, as: "item", in: "$$item.k" }}] } } }, { $project: { // 直接提取目标账户的内容 targetAccount: { $arrayElemAt: [ { $filter: { input: { $objectToArray: "$createdAccounts" }, cond: { $eq: ["$$this.k", email] } }}, 0 ].v } } } ]).toArray().then(results => results[0]?.targetAccount || null);
方式二:用findOne结合$expr
无需聚合,直接在findOne中用表达式匹配,之后提取目标数据:
const email = "test@gmail.com"; let account = await db.collection("users").findOne({ $expr: { $ne: [ { $arrayElemAt: [ { $filter: { input: { $objectToArray: "$createdAccounts" }, cond: { $eq: ["$$this.k", email] } }}, 0 ]}, undefined ] } }).then(doc => doc?.createdAccounts[email] || null);
2. 更新含点嵌套键下的id数组
因为键包含点,不能用常规点路径,有两种高效实现方式:
方式一:动态构建字段路径(推荐)
利用ES6模板字符串拼接含点的字段路径,MongoDB会将其识别为完整键名而非嵌套层级:
const email = "test@gmail.com"; // 示例:向id数组追加元素"d" await db.collection("users").updateOne( // 查询条件,使用上述$expr方式匹配目标文档 { $expr: { $in: [email, { $map: { input: { $objectToArray: "$createdAccounts" }, as: "item", in: "$$item.k" }}] } }, // 用$push追加元素,或根据需求用其他更新操作符 { $push: { [`createdAccounts.${email}.id`]: "d" } } );
方式二:聚合更新(MongoDB 4.2+支持)
通过$objectToArray转换对象为数组,修改后再用$arrayToObject转换回去:
const email = "test@gmail.com"; await db.collection("users").updateOne( { $expr: { $in: [email, { $map: { input: { $objectToArray: "$createdAccounts" }, as: "item", in: "$$item.k" }}] } }, [ { $set: { createdAccounts: { $arrayToObject: { $map: { input: { $objectToArray: "$createdAccounts" }, as: "acc", in: { k: "$$acc.k", v: { $cond: [ { $eq: ["$$acc.k", email] }, // 对目标邮箱的id数组进行拼接 { id: { $concatArrays: ["$$acc.v.id", ["d"]] } }, // 其他账户保持原样 "$$acc.v" ] } } } } } } } ] );
3. 更新操作是拼接还是覆盖?
完全取决于你选择的更新操作符:
- 拼接数组:使用
$push(追加元素)、$addToSet(去重追加)、$concatArrays(聚合中合并数组),这些操作会保留原数组内容并添加新元素。 - 覆盖数组:使用
$set直接赋值,会完全替换原数组内容,例如$set: { [createdAccounts.${email}.id]: ["x", "y"] }会把原数组替换为["x", "y"]。
内容的提问来源于stack exchange,提问作者Luma
相关产品推荐
相关产品推荐

