Atomic MongoDB „findOrCreate”: findOne, wstaw, jeśli nie istnieje, ale nie aktualizuj

114

jak mówi tytuł, chcę wykonać find (jeden) dla dokumentu przez _id, a jeśli nie istnieje, to czy go utworzono, to czy został znaleziony, czy został utworzony, powinien zostać zwrócony w wywołaniu zwrotnym.

Nie chcę go aktualizować, jeśli istnieje, tak jak przeczytałem, że findAndModify robi. Widziałem wiele innych pytań w Stackoverflow dotyczących tego, ale znowu nie chcę niczego aktualizować.

Nie jestem pewien, czy tworząc (lub nieistniejąc), TO w rzeczywistości jest aktualizacja, o której wszyscy mówią, to wszystko jest takie zagmatwane :(

Discipol
źródło

Odpowiedzi:

192

Począwszy od MongoDB 2.4, nie trzeba już polegać na unikatowym indeksie (lub jakimkolwiek innym obejściu) w przypadku findOrCreateoperacji typu atomic .

To właśnie dzięki tej $setOnInsertoperatora new do 2.4, który pozwala określić aktualizacje co powinno nastąpić dopiero po włożeniu dokumentów.

To, w połączeniu z upsertopcją, oznacza, że ​​możesz użyć findAndModifydo wykonania findOrCreateniepodzielnej operacji.

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
})

Ponieważ $setOnInsertdotyczy tylko wstawianych dokumentów, w przypadku znalezienia istniejącego dokumentu żadna modyfikacja nie nastąpi. Jeśli żaden dokument nie istnieje, podniesie go o jeden z podanym _id, a następnie wykona tylko ustawienie wstawiania. W obu przypadkach dokument jest zwracany.

numery1311407
źródło
3
@Gank, jeśli używasz natywnego sterownika mongodb dla węzła, składnia będzie bardziej podobna collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Zobacz dokumentację
numery1311407
7
Czy istnieje sposób, aby dowiedzieć się, czy dokument został zaktualizowany lub wstawiony?
doom
3
Jeśli chcesz sprawdzić, czy powyższe zapytanie ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) wstawia (upsert) do dokumentu, powinieneś rozważyć użycie db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Zwraca, {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}jeśli dokument został wstawiony, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}jeśli dokument już istnieje.
Константин Ван
8
wydaje się być przestarzałe w smaku findOneAndUpdate, findOneAndReplacelubfindOneAndDelete
Ravshan Samandarov
5
Tutaj jednak trzeba być ostrożnym. Działa to tylko wtedy, gdy selektor findAndModify / findOneAndUpdate / updateOne jednoznacznie identyfikuje jeden dokument przez _id. W przeciwnym razie upsert jest dzielony na serwerze na zapytanie i aktualizację / wstawienie. Aktualizacja nadal będzie atomowa. Ale zapytanie i aktualizacja razem nie zostaną wykonane niepodzielnie.
Anon
15

Wersje sterowników> 2

Korzystając z najnowszego sterownika (> wersja 2) , użyjesz findOneAndUpdate jako findAndModifyprzestarzałego. Nowa metoda trwa 3 argumenty, filterThe updateprzedmiot (który zawiera swoje właściwości domyślnych, które należy wprowadzić do nowego obiektu), a optionsgdzie trzeba określić działanie upsert.

Korzystając ze składni obietnicy, wygląda to następująco:

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

const newOrUpdatedDocument = result.value;
hansmaad
źródło
Dzięki, returnOriginalpowinny być returnNewDocumentmyślę - docs.mongodb.com/manual/reference/method/...
Dominic
Nieważne, sterownik węzła zaimplementował to inaczej i twoja odpowiedź jest poprawna
Dominic
6

Jest trochę brudny, ale możesz go po prostu włożyć.

Upewnij się, że klucz ma unikalny indeks (jeśli używasz _id, wszystko w porządku, jest już unikalny).

W ten sposób, jeśli element jest już obecny, zwróci wyjątek, który możesz złapać.

Jeśli go nie ma, nowy dokument zostanie wstawiony.

Zaktualizowano: szczegółowe wyjaśnienie tej techniki w dokumentacji MongoDB

Madarco
źródło
ok, to dobry pomysł, ale dla wcześniej istniejącej wartości zwróci błąd, ale NIE samą wartość, prawda?
Discipol
3
W rzeczywistości jest to jedno z zalecanych rozwiązań dla izolowanych sekwencji operacji (prawdopodobnie znajdź i utwórz, jeśli nie znaleziono) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
numbers1311407
@Discipol Jeśli chcesz wykonać zestaw niepodzielnych operacji, powinieneś najpierw zablokować Dokument, następnie zmodyfikować go, a na koniec zwolnić. Będzie to wymagało większej liczby zapytań, ale możesz zoptymalizować pod kątem najlepszego scenariusza i przez większość czasu wykonywać tylko 1-2 zapytania. patrz: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco
1

Oto co zrobiłem (sterownik Ruby MongoDB):

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

Zaktualizuje go, jeśli istnieje, i wstawi go, jeśli nie.

Vidar
źródło