在MongoDB中更新文档时的条件upsert(insert)

在MongoDB中更新文档时的条件upsert(insert),mongodb,mongodb-query,conditional,upsert,database-update,Mongodb,Mongodb Query,Conditional,Upsert,Database Update,我在MongoDB中有一些文档,看起来是这样的: {type: type1, version: 2, data: ...} {type: type1, version: 3, data: ...} {type: type2, version: 1, data: ...} {type: type2, version: 2, data: ...} ... 我希望更新匹配类型和版本的数据,或者在版本不匹配时为给定类型创建新文档,但希望禁止使用新类型创建新文档 当我这样做时: db.getCollec

我在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: ...}
这正是我想禁止的。
有没有办法在一次呼叫中完成此操作?有没有办法限制某些字段的值?

对于这个用例,我能看到的最好的处理方法是使用,以便在同一请求中发送“update”和“insert”命令。我们还需要在这里有一个唯一的索引,以强制您实际上不创建两个字段的新组合

从这些文件开始:

{ "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" : {

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

        "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}
选项将其标记为“unordered”,它将至少提交没有导致错误的任何指令

在这种情况下,典型的结果是要么“插入”起作用而没有更新,要么“插入”在“更新”适用的地方失败。当响应中返回失败时,您可以检查错误的“索引”是否为
1
,表明预期的“插入”失败,并且由于预期的“重复键”,错误代码是否为
11000


因此,可以忽略“预期”情况下的错误,您只需处理发出的批量指令中不同代码和/或不同位置的“意外”错误。

不要使用upsert。获得新内容的唯一原因是您专门将选项设置为
true
。您还期待什么?正如我在问题中所说:“我想更新匹配类型和版本的数据,或者在版本不匹配时为给定类型创建一个新文档”,然后不使用一个命令。不可以。只需不设置upsert并允许仅更新行为,即可关闭upsert。然后,当操作没有更新任何内容时,可以执行插入操作。您可能会对这两条语句执行大容量操作,即“无序”,这将在尝试插入时忽略重复键错误。这是最好的了。谢谢,这就是我一直在寻找的