在MongoDB中更新文档时,如果条件成立,则执行插入操作。

4

我有一些存储在MongoDB中的文档,它们看起来像这样:

{type: type1, version: 2, data: ...}
{type: type1, version: 3, data: ...}
{type: type2, version: 1, data: ...}
{type: type2, version: 2, data: ...}
...

我希望更新与给定的类型版本匹配的数据,或在版本不匹配时为给定的类型创建一个新文档,但不希望创建具有新类型的新文档。 当我执行以下操作时:

db.getCollection('products').update({"type": "unknown_type", "version" : "99"}, {$set: {"version": 99, "data": new data}}, {"upsert": true})

创建一个新文档。

{type: unknown_type, version: 99, data: ...}

这正是我想要禁止的。 有没有一种方法可以在一次调用中完成此操作?是否有一种方法可以限制某些字段的值?


不要使用upsert。之所以会出现新的内容,是因为你明确将选项设置为“true”。你还期望得到什么? - Neil Lunn
如我在问题中所述:“当类型和版本不匹配时,我想要更新相应数据或为给定类型创建一个新文档”。 - karlitos
不是一个命令就可以实现。不,你可以通过不设置它并允许仅更新行为来关闭upsert。然后,当操作未更新任何内容时,您可以执行插入操作。您可能会使用两个语句进行批量操作,作为“无序”的方式,这将忽略插入尝试中的重复键错误。这大概就是最好的了。 - Neil Lunn
1个回答

3
这种用例中我能想到的最佳处理方式是使用"批量操作",以便在同一请求中发送“更新”和“插入”命令。我们还需要在此处设置唯一索引来强制执行不创建两个字段的新组合。
从这些文件开始:
{ "type" : "type1", "version" : 2 }
{ "type" : "type1", "version" : 3 }
{ "type" : "type2", "version" : 1 }
{ "type" : "type2", "version" : 2 }

在这两个字段上创建一个唯一索引:
db.products.createIndex({ "type": 1, "version": 1 },{ "unique": true })

然后我们尝试做一些实际的插入,使用批量操作来执行更新和插入:
db.products.bulkWrite(
  [
     { "updateOne": {
       "filter": { "type": "type3", "version": 1 },
       "update": { "$set": { "data": {} } }
     }},
     { "insertOne": {
       "document": { "type": "type3", "version": 1, "data": { } }
     }}
  ],
  { "ordered": false }
)

我们应该得到这样的回复:
{
        "acknowledged" : true,
        "deletedCount" : 0,
        "insertedCount" : 1,
        "matchedCount" : 0,
        "upsertedCount" : 0,
        "insertedIds" : {
                "1" : ObjectId("594257b6fc2a40e470719470")
        },
        "upsertedIds" : {

        }
}

注意这里的matchedCount0,反映了“更新”操作:
        "matchedCount" : 0,

如果我再做同样的事情,但使用不同的数据:
db.products.bulkWrite(
  [
     { "updateOne": {
       "filter": { "type": "type3", "version": 1 },
       "update": { "$set": { "data": { "a": 1 } } }
     }},
     { "insertOne": {
       "document": { "type": "type3", "version": 1, "data": { "a": 1 } }
     }}
  ],
  { "ordered": false }
)

然后我们看到:
BulkWriteError({
        "writeErrors" : [
                {
                        "index" : 1,
                        "code" : 11000,
                        "errmsg" : "E11000 duplicate key error collection: test.products index: type_1_version_1 dup key: { : \"type3\", : 1.0 }",
                        "op" : {
                                "_id" : ObjectId("5942583bfc2a40e470719471"),
                                "type" : "type3",
                                "version" : 1,
                                "data" : {
                                        "a" : 1
                                }
                        }
                }
        ],
        "writeConcernErrors" : [ ],
        "nInserted" : 0,
        "nUpserted" : 0,
        "nMatched" : 1,
        "nModified" : 1,
        "nRemoved" : 0,
        "upserted" : [ ]
})

这将在所有驱动程序中始终抛出错误,但我们也可以在响应的详细信息中看到:
        "nMatched" : 1,
        "nModified" : 1,

这意味着即使“插入”失败了,“更新”实际上也完成了它的工作。这里需要注意的重要事情是,虽然“批处理”中可能会出现“错误”,但如果它们是我们预测的类型,即我们预期的重复键错误代码11000,那么我们可以处理它们。
因此,最终的数据当然如下所示:
{ "type" : "type1", "version" : 2 }
{ "type" : "type1", "version" : 3 }
{ "type" : "type2", "version" : 1 }
{ "type" : "type2", "version" : 2 }
{ "type" : "type3", "version" : 1, "data" : { "a" : 1 } }

这是您想在这里实现的目标。
因此,通过使用{"ordered": false}选项将其标记为“无序”,.bulkWrite()操作将产生异常,但至少会提交未导致错误的任何指令。
在这种情况下,通常的结果是“插入”成功并且没有更新,或者“插入”失败而“更新”应用。当响应返回失败时,您可以检查错误的“索引”是否为1,表示预期的“插入”失败,错误代码为11000,因为出现了预期的“重复键”。
因此,“预期”的错误可以被忽略,您只需要处理“意外”的错误,例如不同的代码和/或不同的发出批量指令的位置。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接