MongoDB: вложенный документ upsert

у меня есть документы, которые выглядят примерно так, с уникальным индексом на bars.name:

{ name: 'foo', bars: [ { name: 'qux', somefield: 1 } ] }

. Я хочу либо обновить под-документ, где { name: 'foo', 'bars.name': 'qux' } и $set: { 'bars.$.somefield': 2 }, или создать новый под-документ с { name: 'qux', somefield: 2 } под { name: 'foo' }.

можно ли это сделать с помощью одного запроса с upsert,или мне придется выпустить два отдельных?

по теме: 'upsert' во встроенном документе (предлагает изменить схему имейте идентификатор поддокумента в качестве ключа, но это было два года назад, и мне интересно, есть ли лучшие решения сейчас.)

3 ответов


нет, на самом деле нет лучшего решения для этого, поэтому, возможно, с объяснением.

Предположим, у вас есть документ, который имеет структуру, как вы показываете:

{ 
  "name": "foo", 
  "bars": [{ 
       "name": "qux", 
       "somefield": 1 
  }] 
}

если вы делаете обновление такой

db.foo.update(
    { "name": "foo", "bars.name": "qux" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)

тогда все в порядке, потому что был найден соответствующий документ. Но если вы измените значение " bars.имя":

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } },
    { "upsert": true }
)

тогда вы получите неудачу. Единственное, что действительно изменилось-это то, что в MongoDB 2.6 и выше ошибка немного более лаконична:

WriteResult({
    "nMatched" : 0,
    "nUpserted" : 0,
    "nModified" : 0,
    "writeError" : {
        "code" : 16836,
        "errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: bars.$.somefield"
    }
})

это лучше в некотором смысле, но вы действительно не хотите "upsert" в любом случае. Что вы хотите сделать, это добавить элемент в массив, где "имя" в настоящее время не существует.

Итак, что вы действительно хотите, это "результат" от попытки обновления без флага "upsert", чтобы увидеть, были ли затронуты какие-либо документы:

db.foo.update(
    { "name": "foo", "bars.name": "xyz" },
    { "$set": { "bars.$.somefield": 2 } }
)

получая в ответ:

WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })

Итак, когда измененные документы are 0 тогда вы знаете, что хотите выпустить следующее обновление:

db.foo.update(
    { "name": "foo" },
    { "$push": { "bars": {
        "name": "xyz",
        "somefield": 2
    }}
)

на самом деле нет другого способа сделать именно то, что вы хотите. Поскольку дополнения к массиву не являются строго "заданным" типом операции, вы не можете использовать $addToSet в сочетании с "массовое обновление" функциональность там, так что вы можете "каскадировать" свои запросы на обновление.

в этом случае кажется, что вам нужно проверить результат или иным образом принять чтение всего документа и проверка необходимости обновления или вставки нового элемента массива в код.


Если вы не против немного изменить схему и иметь такую структуру:

{ "name": "foo", "bars": { "qux": { "somefield": 1 },
                           "xyz": { "somefield": 2 },
                  }
}

вы можете выполнять свои операции за один раз. Вновь 'upsert' во встроенном документе комплектность


нет способа сделать это, если обновление опирается на ссылку на обновляемую запись (например, обновление x => x + 1). Выдача 2 отдельных команд (set then insert) приводит к состоянию гонки, которое не может быть решено путем проверки дубликатов, потому что, если ваша вставка отклонена из-за дубликата, вы потеряете эффект вашего обновления (например, x не будет увеличиваться должным образом в приведенном выше примере). Было бы неплохо, если бы MongoDB добавил эту функциональность, так как возможность реализации встроенные документы являются большой привлекательностью для MongoDB для определенных приложений.