Подтвердить что ты не робот

MongoDB: вспомогательный субподряд

У меня есть документы, которые выглядят примерно так: уникальный индекс на 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' во встроенном документе (предлагает изменить схему, чтобы иметь идентификатор поддокумента в качестве ключа, но это от двух лет назад и мне интересно, есть ли сейчас лучшие решения.)

4b9b3361

Ответ 1

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

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

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

Если вы сделаете обновление, подобное этому

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

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

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", чтобы увидеть, были ли затронуты какие-либо документы:

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

Выход в ответ:

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

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

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

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

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

Ответ 2

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

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

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