使用新字段插入数据或使用 updateOne mongodb 有条件地更新

Insert data with new fields OR update conditionally using updateOne mongodb

我有一个格式如下的文档:

{
  "f1": "v1",
  "f2": {
     "id": 1,
     "sub": "subv",
     "updatedAt": 123
   }
}

我有另一个来源给我一个 inputf2 对象。 我想编写一个更新插入查询来查找具有匹配过滤器 {"f2.id": inputf2.id} 的文档。 I̶f̶ ̶f̶o̶u̶n̶d̶ ̶a̶n̶d̶ ̶i̶f̶ ̶f̶2̶.̶u̶p̶d̶a̶t̶e̶d̶A̶t̶ ̶<̶ ̶i̶n̶p̶u̶t̶f̶2̶.̶u̶p̶d̶a̶t̶e̶d̶A̶t̶ ̶t̶h̶e̶n̶ ̶u̶p̶d̶a̶t̶e̶ ̶w̶h̶o̶l̶e̶ ̶f̶2̶ ̶t̶o̶ ̶i̶n̶p̶u̶t̶f̶2̶.̶ ̶O̶t̶h̶e̶r̶w̶i̶s̶e̶,̶ ̶i̶n̶s̶e̶r̶t̶ ̶a̶ ̶n̶e̶w̶ ̶d̶o̶c̶u̶m̶e̶n̶t̶ ̶w̶i̶t̶h̶ ̶n̶e̶w̶ ̶f̶i̶e̶l̶d̶ ̶f̶1̶ ̶(̶v̶1̶ ̶i̶s̶ ̶a̶ ̶h̶a̶r̶d̶c̶o̶d̶e̶d̶ ̶v̶a̶l̶u̶e̶)̶.

编辑更清晰的逻辑:

示例:

输入:

{"id": 1,"sub": "newsubv","updatedAt": 100}

因为100 < 123.id = 1 不会被更新。 输出:

{
  "f1": "v1",
  "f2": {"id": 1,"sub": "subv","updatedAt": 123}
}

输入:

{"id": 1,"sub": "newsubv","updatedAt": 150}

因为150 > 123.id = 1 会被更新。 输出:

{
  "f1": "v1",
  "f2": {"id": 1,"sub": "newsubv","updatedAt": 150}
}

输入:

{"id": 2,"sub": "newsubv","updatedAt": 100}

因为在db中没有找到input id = 2。无论 updatedAt 是什么,它都会被插入。 输出:

{
  "f1": "v1",
  "f2": {"id": 1,"sub": "subv","updatedAt": 123}
},
{
  "f1": "v1",
  "f2": {"id": 2,"sub": "newsubv","updatedAt": 100}
}

我尝试了两种类型的更新,更新文档或更新聚合管道,但似乎其中任何一种都符合我的要求。

有更新文件

我可以使用 $setOnInsert 但不能将 f2 设置为 f2.updatedAt < inputf2.updatedAt 条件:

db.collection.update({
  "f2.id": "1"
},
{
  "$set": {
    // Can not check updatedAt = 100 < 123 and then do nothing
    "f2": {
      "id": 1
      "sub": "newsubv",
      "updatedAt": 100
    }
  },
  "$setOnInsert": {
    "f1": "v1"
  }
},
{"upsert": true});

有更新聚合管道

我可以通过将 f2 克隆到新字段 oldf2,用 f2.updatedAt < inputf2.updatedAt 条件更新 f2,然后应用条件并删除 oldf2.但是在聚合中,没有$setOnItem函数:

db.test.updateOne(
{"f2.id": 1},
[
    {$set: {"oldf2": "$f2"}},
    {
        $set: {
            "f2": {
                $cond: [{$lt: ["$f2.updatedAt", 100]}, {
                    "id": 1,
                    "sub": "newsubv",
                    "updatedAt": 100
                }, "$oldf2"]
            }
        }
    },
    {$set: {"oldf2": "$$REMOVE"}},
    // How to apply something like $setOnInsert function?
],
{"upsert":true})

我必须使用 updateOne 因为有 f2 项目的列表,我想在 bulkWrite.

中批量更新这些项目

已注明f2.id 是集合中的唯一字段

任何人都可以帮我写这个逻辑的查询吗?非常感谢你们。

您几乎可以像以前一样使用第一种方法,只是有一个条件。

编辑:但是如果你想在插入的情况下改变f2.id,你应该分开f2,像这样:

db.collection.update({
  "f2.id": 1,
  "f2.updatedAt": {
    $lte: inputf2.updatedAt // Here you can check updatedAt = 100 < 123 and then stop updating
  }
},
{
  "$set": {
    "f2.sub": "newsubv",
    "f2.updatedAt": 100
  },
  "$setOnInsert": {
    "f1": "v1",
    "f2.id": newF2,
  }
},
{
  "upsert": true
})

你可以在 playground 上看到它 您可以将 inputf2.updatedAt 设置为 100 或 150 以查看我在此处粘贴的两种情况:

如果f2.updatedAt >= inputf2.updatedAt,我们将在数据库中得到两个不同的文档。新的将有 inputf2.updatedAt,但有新的 ID,因为这应该是一个唯一的 ID:

[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "f1": "v1",
    "f2": {
      "id": 1,
      "sub": "subv",
      "updatedAt": 123
    }
  },
  {
    "_id": ObjectId("625b1873cdbb97ce928d8898"),
    "f1": "v1",
    "f2": {
      "id": 2,
      "sub": "newsubv",
      "updatedAt": 100
    }
  }
]

否则,f2.updatedAt < inputf2.updatedAt : 数据库上的文档将被更新,但将保留其原始 ID:

  {
    "_id": ObjectId("5a934e000102030405000000"),
    "f1": "v1",
    "f2": {
      "id": 1,
      "sub": "newsubv",
      "updatedAt": 150
    }
  }

顺便说一句,在批量更新中使用此方法没有问题

终于可以写查询了。这是我的解决方案:

db.test.updateOne(
{"f2.id": 1},
[{
  $set: {
    "f2": { $cond: [
        {$lt: ["$f2.updatedAt", 100]}, // time check condition
        {"id": 1,"sub": "newsubv","updatedAt": 100}, // overwrite value
        "$f2" // no change, return origin value
    ]},
    "f1": {$ifNull: ["$f1", "v1"]}, // this can do the same as $setOnInsert
  }
}],
{"upsert":true}
)

测试用例:

输入 1:Mongo Playground

{"id": 1,"sub": "newsubv","updatedAt": 100}

输入 2:Mongo Playground

{"id": 1,"sub": "newsubv","updatedAt": 200}

输入 3:Mongo Playground

{"id": 2,"sub": "newsubv","updatedAt": 100}