Условный upsert (вставка) при обновлении документа в MongoDB
У меня есть несколько документов в 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: ...}
это именно то, что я хотел бы запретить. Есть ли способ сделать эту операцию за один звонок? Есть ли способ ограничить значения для некоторых полей?
1 ответ
Лучшая обработка, которую я вижу для этого варианта использования, - это использование "Bulk Operations" для отправки команд "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 }
возможность .bulkWrite()
тогда он по крайней мере передаст любые инструкции, которые не привели к ошибке.
В этом случае типичным результатом является то, что либо "вставка" работает, а обновления не происходит, либо "вставка" завершается неудачно, когда применяется "обновление". Когда ошибка возвращается в ответе, вы можете проверить "индекс" ошибки 1
указывает на ожидаемую ошибку "вставки" и код ошибки 11000
из-за ожидаемого "дубликата ключа".
Поэтому ошибки в "ожидаемом" случае можно игнорировать, и вам нужно будет только обработать "неожиданные" ошибки для другого кода и / или другой позиции в выданной массовой инструкции.