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 для определенных приложений.