MongoDB атомний “findOrCreate”: findOne, вставити, якщо його немає, але не оновлювати


114

як зазначено в заголовку, я хочу виконати пошук (один) для документа за допомогою _id, а якщо його немає, чи створили його, то чи був він знайдений чи створений, чи повертається він у зворотний виклик.

Я не хочу оновлювати його, якщо він існує, як я читав findAndModify. Я бачив багато інших питань щодо Stackoverflow щодо цього, але знову ж таки, не хочу нічого оновлювати.

Я не впевнений, що, створюючи (не існує), ТО, що насправді є оновленням, про яке всі говорять, це все так заплутано :(

Відповіді:


192

Починаючи з MongoDB 2.4, більше не потрібно покладатися на унікальний індекс (або будь-який інший спосіб вирішення) для атомних findOrCreate операцій.

Це стало можливим завдяки в $setOnInsertоператора нового 2.4, яка дозволяє задати поновлення , які повинні відбутися тільки при вставці документів.

Це в поєднанні з upsertопцією означає, що ви можете використовувати findAndModifyдля досягнення атомарної findOrCreateоперації.

db.collection.findAndModify({
  query: { _id: "some potentially existing id" },
  update: {
    $setOnInsert: { foo: "bar" }
  },
  new: true,   // return new doc if one is upserted
  upsert: true // insert the document if it does not exist
})

Оскільки стосується $setOnInsertлише вставлених документів, якщо вже знайдений документ, зміни не відбудуться. Якщо жодного документа не існує, він вставить один із вказаним _id, а потім виконати лише набір вставок. В обох випадках документ повертається.


3
@Gank, якщо ви використовуєте власний драйвер mongodb для вузла, синтаксис буде більше схожим collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Дивіться документи
номери1311407

7
Чи є спосіб дізнатися, чи документ було оновлено чи вставлено?
дум

3
Якщо ви хочете перевірити, чи запит над ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) вставляв (upsert) редагував документ, вам варто розглянути можливість використання db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Він повертається, {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}якщо документ вставлено, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}якщо документ вже існує.
Константин Ван

8
здається, застаріла на смак findOneAndUpdate, findOneAndReplaceабоfindOneAndDelete
Равшан Самандаров

5
Тут потрібно бути обережними. Це працює лише в тому випадку, якщо селектор findAndModify / findOneAndUpdate / updateOne однозначно ідентифікує один документ за допомогою _id. Інакше сервер розбивається на сервер на запит та оновлення / вставку. Оновлення все ще буде атомним. Але запит та оновлення разом не будуть виконуватися атомно.
Anon

15

Версії драйверів> 2

Використовуючи останній драйвер (> версія 2) , ви будете використовувати findOneAndUpdate як findAndModifyзастарілий. Новий метод приймає 3 аргумент, filter, то updateоб'єкт (який містить ваші властивості по замовчуванню, які повинні бути вставлені для нового об'єкта), і optionsде ви повинні вказати операцію upsert.

Використовуючи синтаксис обіцянки, це виглядає приблизно так:

const result = await collection.findOneAndUpdate(
  { _id: new ObjectId(id) },
  {
    $setOnInsert: { foo: "bar" },
  },
  {
    returnOriginal: false,
    upsert: true,
  }
);

const newOrUpdatedDocument = result.value;

Дякую, чи returnOriginalварто returnNewDocumentдумати - docs.mongodb.com/manual/reference/method/…
Домінік

Не забувайте, драйвер вузла реалізував його по-іншому, і ваша відповідь правильна
Домінік

6

Це трохи брудно, але ви можете просто вставити його.

Будьте впевнені, що ключ має унікальний індекс на ньому (якщо ви використовуєте _id це нормально, він вже унікальний).

Таким чином, якщо елемент вже присутній, він поверне виняток, який ви можете зловити.

Якщо його немає, новий документ буде вставлено.

Оновлено: детальне пояснення цієї методики в документації MongoDB


ОК, це гарна ідея, але для попереднього значення воно поверне помилку, але НЕ саме значення, правда?
Дисципол

3
Це фактично одне з рекомендованих рішень для ізольованих послідовностей операцій (знайдіть потім створіть, якщо їх не знайдено, імовірно) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
числа1311407

@Discipol, якщо ви хочете виконати набір атомних операцій, спершу слід заблокувати документ, потім модифікувати його, а в кінці випустити його. Для цього знадобиться більше запитів, але ви можете оптимізувати для найкращого сценарію справи і робити лише 1-2 запити в більшості випадків. дивіться: docs.mongodb.org/manual/tutorial/perform-two-phase-commiss
Madarco

1

Ось що я зробив (драйвер Ruby MongoDB):

$db[:tags].update_one({:tag => 'flat'}, {'$set' => {:tag => 'earth' }}, { :upsert => true })}

Він буде оновити його, якщо він існує, і вставити його, якщо його немає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.