You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何更新含点的嵌套键?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 05:14:50