MongoDB atomique «findOrCreate»: findOne, insérer si inexistant, mais ne pas mettre à jour


114

comme le titre l'indique, je veux effectuer une recherche (un) pour un document, par _id, et s'il n'existe pas, le faire créer, puis s'il a été trouvé ou créé, le renvoyer dans le rappel.

Je ne veux pas le mettre à jour s'il existe, comme j'ai lu le fait findAndModify. J'ai vu beaucoup d'autres questions sur Stackoverflow à ce sujet, mais encore une fois, je ne souhaite rien mettre à jour.

Je ne sais pas si en créant (ou non), CELA est en fait la mise à jour dont tout le monde parle, c'est tellement déroutant :(

Réponses:


192

À partir de MongoDB 2.4, il n'est plus nécessaire de s'appuyer sur un index unique (ou toute autre solution de contournement) pour les findOrCreateopérations de type atomique .

C'est grâce à l' $setOnInsertopérateur nouveau à 2.4, qui vous permet de spécifier des mises à jour qui ne devraient se produire que lors de l'insertion de documents.

Ceci, combiné avec l' upsertoption, signifie que vous pouvez utiliser findAndModifypour réaliser une findOrCreateopération de type atomique .

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

Comme $setOnInsertn'affecte que les documents insérés, si un document existant est trouvé, aucune modification ne se produira. Si aucun document n'existe, il en insérera un avec le _id spécifié, puis effectuera l'insertion uniquement définie. Dans les deux cas, le document est retourné.


3
@Gank si vous utilisez le pilote natif mongodb pour le nœud, la syntaxe ressemblerait davantage à collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Voir la documentation
numéros1311407

7
Existe-t-il un moyen de savoir si le document a été mis à jour ou inséré?
doom

3
Si vous voulez vérifier si la requête ci-dessus ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) insert (upsert) a édité un document, vous devriez envisager d'utiliser db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Il renvoie {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}si le document est inséré, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}si le document existe déjà.
Константин Ван

8
semble être obsolète dans la saveur de findOneAndUpdate, findOneAndReplaceoufindOneAndDelete
Ravshan Samandarov

5
Il faut cependant être prudent ici. Cela ne fonctionne que si le sélecteur de findAndModify / findOneAndUpdate / updateOne identifie de manière unique un document par _id. Sinon, l'upsert est divisé sur le serveur en une requête et une mise à jour / insertion. La mise à jour sera toujours atomique. Mais la requête et la mise à jour ensemble ne seront pas exécutées de manière atomique.
Anon

15

Versions de pilotes> 2

En utilisant le dernier pilote (> version 2) , vous utiliserez findOneAndUpdate car il findAndModifyétait obsolète. La nouvelle méthode prend 3 arguments, le filter, l' updateobjet (qui contient vos propriétés par défaut, qui doit être inséré pour un nouvel objet), et optionsoù vous devez spécifier l'opération upsert.

En utilisant la syntaxe de la promesse, cela ressemble à ceci:

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

const newOrUpdatedDocument = result.value;

Merci, returnOriginaldevrait être returnNewDocumentje pense - docs.mongodb.com/manual/reference/method/…
Dominic

Peu importe, le pilote de nœud l'a implémenté différemment et votre réponse est correcte
Dominic

6

C'est un peu sale, mais vous pouvez simplement l'insérer.

Assurez-vous que la clé a un index unique dessus (si vous utilisez le _id c'est ok, il est déjà unique).

De cette façon, si l'élément est déjà présent, il renverra une exception que vous pouvez intercepter.

S'il n'est pas présent, le nouveau document sera inséré.

Mise à jour: une explication détaillée de cette technique sur la documentation MongoDB


ok, c'est une bonne idée, mais pour la valeur préexistante, il renverra une erreur mais PAS la valeur elle-même, non?
Discipol

3
Ceci est en fait l' une des solutions recommandées pour les séquences isolées d'opérations (trouver puis créer si introuvable, probablement) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
numbers1311407

@Discipol si vous souhaitez effectuer un ensemble d'opérations atomiques, vous devez d'abord verrouiller le document, puis le modifier et à la fin le libérer. Cela nécessitera plus de requêtes, mais vous pouvez optimiser pour le meilleur scénario et ne faire que 1 à 2 requêtes la plupart du temps. voir: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco

1

Voici ce que j'ai fait (pilote Ruby MongoDB):

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

Il le mettra à jour s'il existe et l'insérera si ce n'est pas le cas.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.