我有一个以下格式的Collection :

[
  {
    "postId": ObjectId("62dffd0acb17483cf015375f"),
    "userId": ObjectId("62dff9584f5b702d61c81c3c"),
    "state": [
      {
        "id": ObjectId("62dffc49cb17483cf0153220"),
        "notes": "these are my custom notes!",
        "lvl": 3,
        
      },
      {
        "id": ObjectId("62dffc49cb17483cf0153221"),
        "notes": "hello again",
        "lvl": 0,
      },
    ]
  },
]

我的目标是能够在以下情况下更新和添加此数组中的元素:

  1. 如果新元素的ID不在state数组中,则将新元素推送到数组中
  2. 如果新元素的ID在state数组中,并且其lvl字段为0,则使用新信息更新该元素
  3. 如果数组中存在新元素的ID,并且它的lvl字段不是0,那么应该不会发生任何事情.我将抛出一个错误,因为我看到没有匹配的文档.

基本上,为了实现这一点,我考虑在upsert中使用findOneAndUpdate,但我不确定如何告诉查询在lvl为0时更新状态,或者如果找到匹配项时它大于0,则不执行任何操作.

对于解决(1),这是我能想到的:

db.collection.findOneAndUpdate(
    {
        "postId": ObjectId("62dffd0acb17483cf015375f"),
        "userId": ObjectId("62dff9584f5b702d61c81c3c"),
        "state.id": {
            "$ne": ObjectId("62dffc49cb17483cf0153222"),
        },
    },
    {
        "$push": {"state": {"id": ObjectId("62dffc49cb17483cf0153222"), "lvl": 1}}
    },
    {
        "new": true,
        "upsert": true,
    }
)

处理这个问题的正确方式是什么?我是否应该将查询拆分成多个查询?

Edit:到目前为止,我已经在多个查询(一个获取文档,然后迭代其状态数组以判断其中是否存在ID,然后在一个普通的if-Else子句中执行(1)、(2)和(3))中完成了此操作)

推荐答案

If the ID of the new element exists in the array, and its lvl field is not 0, then nothing should happen. I will throw an error by seeing that no documents where matched.

First thingFYI,

  • 在嵌套数组中不能向上插入
  • Upsert不会向数组中添加新元素
  • Upsert可以添加具有新元素的新文档
  • 如果您想在记录不存在的情况下抛出错误,则不需要upsert

Second thing,在MongoDB 4.2中使用update with aggregation pipeline,可以在一个查询中实现这一点,

注意:我必须在这里通知您,此查询将响应更新的文档,但将没有标志或任何线索,如果此查询满足您的第一种情况或第二种情况,或者第三种情况,您必须通过查询响应签入您的客户端代码.

  • 仅判断postIduserId字段的条件
  • 我们将更新$set阶段下的state个场
  • 判断提供的ID是否存在于stateid中?
    • TRUE,$map表示迭代state数组的循环
      • 判断idlvl: 0的条件?
        • 为True,则为$mergeObjects,则将当前对象与新信息合并
        • False,则不会执行任何操作
    • False,然后将新元素添加到state数组中,再乘以$concatArrays运算符
db.collection.findOneAndUpdate(
  {
    postId: ObjectId("62dffd0acb17483cf015375f"),
    userId: ObjectId("62dff9584f5b702d61c81c3c")
  },
  [{
    $set: {
      state: {
        $cond: [
          { $in: [ObjectId("62dffc49cb17483cf0153221"), "$state.id"] },
          {
            $map: {
              input: "$state",
              in: {
                $cond: [
                  {
                    $and: [
                      { $eq: ["$$this.id", ObjectId("62dffc49cb17483cf0153221")] },
                      { $eq: ["$$this.lvl", 0] }
                    ]
                  },
                  {
                    $mergeObjects: [
                      "$$this",
                      { 
                        // update your new fields here
                        "notes": "new note" 
                      }
                    ]
                  },
                  "$$this"
                ]
              }
            }
          },
          {
            $concatArrays: [
              "$state",
              [
                // add new element
                {
                  "id": ObjectId("62dffc49cb17483cf0153221"),
                  "lvl": 1
                }
              ]
            ]
          }
        ]
      }
    }
  }],
  { returnNewDocument: true }
)

Playrgound

Third thing,您可以执行2个更新查询,

  • 对于case:Element的第一个查询不存在,它将推送state中的一个新元素
let response = db.collection.findOneAndUpdate({
  postId: ObjectId("62dffd0acb17483cf015375f"),
  userId: ObjectId("62dff9584f5b702d61c81c3c"),
  "state.id": { $ne: ObjectId("62dffc49cb17483cf0153221") }
},
{
  $push: {
    state: {
      id: ObjectId("62dffc49cb17483cf0153221"),
      lvl: 1
    }
  }
},
{
  returnNewDocument: true
})

第二查询基于如果上述查询的响应是null则该查询将被执行,

  • 这将判断状态idlvl: 0条件如果条件满足,则执行更新字段操作,如果未找到文档,则返回NULL
  • 如果这将返回NULL,则可以抛出异常,否则使用响应数据和响应成功
if (response == null) {
  response = db.collection.findOneAndUpdate({
    postId: ObjectId("62dffd0acb17483cf015375f"),
    userId: ObjectId("62dff9584f5b702d61c81c3c"),
    state: { 
      $elemMatch: {
        id: ObjectId("62dffc49cb17483cf0153221"),
        lvl: 0
      }
    }
  },
  {
    $set: {
      // add your update fields
      "state.$.notes": "new note"
    }
  },
  {
    returnNewDocument: true
  });
  
  // not found and throw an error
  if (response == null) {
    return {
      // throw error;
    };
  }
}

// do stuff with "response" data and return result

return {
  // success;
};

注意:根据上面的选项,我建议您在Third thing中解释过,您可以执行2个更新查询.

Mongodb相关问答推荐

在MongoDB中使用explain()和查询时缺少winningPlan''''

没有文档的MongoDB集合中的不一致,但当我执行count()时,它告诉我有15个文档

联接不返回具有ObjectId和非ObjectId的结果

如何匹配 MongoDB 中同一文档中两个字段的比较?

如何对 MongoDB setWindowFields 中当前文档以外的文档进行操作

MongoDb $filter,然后获取非重复计数

MongoDB 使用 pymongo 收集 500K 文档的写入速度很差

如何过滤查找mongodb的结果

在亚马逊 EC2 上托管 nodeJS/mongoose Web 应用程序

从 node.js 的 mongodb 集合中删除文档

创建索引需要很长时间

GeoJSON 和 MongoDB:将点存储为 GeoJSON.Point 是否值得?

MongoDB - 清除嵌套数组中的元素

如何使用原子操作在一个文档中切换布尔字段?

单个模式数组中的多个模式引用 - mongoose

Mongodb KeyFile 太开放权限

在 mongodb 中的索引列上查找重复项的快速方法

MongoDb 聚合 $match 错误:Arguments must be aggregate pipeline operators

mongo中的稀疏索引和空值

如何使用 mgo 从 golang 中的 mongodb 集合中 Select 所有记录