Условный 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 из-за ожидаемого "дубликата ключа".

Поэтому ошибки в "ожидаемом" случае можно игнорировать, и вам нужно будет только обработать "неожиданные" ошибки для другого кода и / или другой позиции в выданной массовой инструкции.

Другие вопросы по тегам