MongoDB atomico "findOrCreate": findOne, inserire se inesistente, ma non aggiornare


114

come dice il titolo, voglio eseguire una ricerca (uno) per un documento, da _id, e se non esiste, averlo creato, quindi se è stato trovato o creato, lo faccio restituire nella richiamata.

Non voglio aggiornarlo se esiste, come ho letto findAndModify. Ho visto molte altre domande su Stackoverflow riguardo a questo, ma ancora una volta, non desidero aggiornare nulla.

Non sono sicuro che creando (o non esistendo), QUESTO sia effettivamente l'aggiornamento di cui tutti parlano, è tutto così confuso :(

Risposte:


192

A partire da MongoDB 2.4, non è più necessario fare affidamento su un indice univoco (o qualsiasi altra soluzione alternativa) per findOrCreateoperazioni di tipo atomico .

Questo grazie a l' $setOnInsertoperatore di nuovo a 2.4, che consente di specificare gli aggiornamenti che dovrebbe avvenire solo quando l'inserimento dei documenti.

Questo, combinato con l' upsertopzione, significa che puoi usarlo findAndModifyper ottenere un'operazione di findOrCreatetipo atomico .

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

Poiché $setOnInsertriguarda solo i documenti inseriti, se viene trovato un documento esistente, non verrà eseguita alcuna modifica. Se non esiste alcun documento, ne inserirà uno con l'_id specificato, quindi eseguirà il set di solo inserimento. In entrambi i casi, il documento viene restituito.


3
@Gank se stai usando il driver nativo mongodb per il nodo, la sintassi sarebbe più simile collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Vedi i documenti
numeri1311407

7
C'è un modo per sapere se il documento è stato aggiornato o inserito?
Doom

3
Se vuoi controllare se la query sopra ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) inserisce (upsert) un documento, dovresti considerare di usare db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Ritorna {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}se il documento è stato inserito, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}se il documento esiste già.
Константин Ван

8
sembra essere deprecato nel sapore di findOneAndUpdate, findOneAndReplaceofindOneAndDelete
Ravshan Samandarov

5
Bisogna stare attenti qui, però. Funziona solo se il selettore di findAndModify / findOneAndUpdate / updateOne identifica in modo univoco un documento per _id. In caso contrario, l'upsert viene suddiviso sul server in una query e un aggiornamento / inserimento. L'aggiornamento sarà comunque atomico. Ma la query e l'aggiornamento insieme non verranno eseguiti atomicamente.
Anon

14

Versioni driver> 2

Utilizzando il driver più recente (> versione 2) , utilizzerai findOneAndUpdate poiché findAndModifyera deprecato. Il nuovo metodo accetta 3 argomenti, il filter, l' updateoggetto (che contiene le tue proprietà predefinite, che dovrebbe essere inserito per un nuovo oggetto), e optionsdove devi specificare l'operazione di upsert.

Usando la sintassi della promessa, assomiglia a questo:

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

const newOrUpdatedDocument = result.value;

Grazie, returnOriginaldovrebbe essere returnNewDocumentcredo - docs.mongodb.com/manual/reference/method/…
Dominic

Non importa, il driver del nodo lo ha implementato in modo diverso e la tua risposta è corretta
Dominic

6

È un po 'sporco, ma puoi semplicemente inserirlo.

Assicurati che la chiave abbia un indice univoco (se usi _id va bene, è già unico).

In questo modo se l'elemento è già presente restituirà un'eccezione che potrai catturare.

Se non è presente, verrà inserito il nuovo documento.

Aggiornato: una spiegazione dettagliata di questa tecnica nella documentazione di MongoDB


ok, questa è una buona idea, ma per il valore preesistente restituirà un errore ma NON il valore stesso, giusto?
Discipol

3
Questo è in realtà una delle soluzioni consigliate per le sequenze isolate di operazioni (trovare quindi creare se non viene trovato, presumibilmente) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
numbers1311407

@Discipol se si desidera eseguire una serie di operazioni atomiche, è necessario prima bloccare il documento, quindi modificarlo e alla fine rilasciarlo. Ciò richiederà più query, ma puoi ottimizzare per lo scenario migliore ed eseguire solo 1-2 query la maggior parte delle volte. vedi: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco

1

Ecco cosa ho fatto (driver Ruby MongoDB):

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

Lo aggiornerà se esiste e lo inserirà in caso contrario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.