124 votes

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

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

Je ne veux pas le mettre à jour s'il existe, comme j'ai lu que findAndModify le fait. J'ai vu de nombreuses autres questions sur Stackoverflow à ce sujet, mais là encore, je ne souhaite pas mettre à jour quoi que ce soit.

Je ne suis pas sûr qu'en créant (ou en n'existant pas), CELA soit la mise à jour dont tout le monde parle, c'est tellement confus :(

204voto

numbers1311407 Points 15653

À partir de la version 2.4 de MongoDB, il n'est plus nécessaire de recourir à un index unique (ou à toute autre solution de contournement) pour les données atomiques. findOrCreate comme des opérations.

Ceci grâce à le site $setOnInsert opérateur nouveau dans la 2.4, qui vous permet de spécifier les mises à jour qui doivent se produire uniquement lors de l'insertion de documents.

Ceci, combiné à la upsert signifie que vous pouvez utiliser findAndModify pour atteindre un niveau atomique findOrCreate -comme une opération.

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 $setOnInsert n'affecte que les documents en cours d'insertion. Si un document existant est trouvé, aucune modification ne sera effectuée. Si aucun document n'existe, il en insère un avec l'_id spécifié, puis exécute l'ensemble d'insertion seulement. Dans les deux cas, le document est retourné.

0 votes

Votre code est utilisé dans le shell mongodb, mais comment l'utiliser dans le code node.js ?

3 votes

@Gank si vous utilisez le pilote natif mongodb pour node, la syntaxe serait plutôt du type collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback) . Voir les docs

8 votes

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

23voto

hansmaad Points 4842

Versions du pilote > 2

Utilisation du dernier pilote (> version 2) vous utiliserez findOneAndUpdate comme findAndModify a été déprécié. La nouvelle méthode prend 3 arguments, le filter le update (qui contient vos propriétés par défaut, qui doivent être insérées pour un nouvel objet), et options où vous devez spécifier l'opération d'upsert.

En utilisant la syntaxe des promesses, cela ressemble à ceci :

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

const newOrUpdatedDocument = result.value;

6voto

Madarco Points 956

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

Assurez-vous que la clé possède un index unique (si vous utilisez l'_id, c'est bon, il est déjà unique).

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

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

Mis à jour : une explication détaillée de cette technique sur le site Documentation MongoDB

0 votes

Ok, c'est une bonne idée, mais pour la valeur préexistante, cela retournera une erreur mais PAS la valeur elle-même, n'est-ce pas ?

3 votes

Il s'agit en fait d'une des solutions recommandées pour les séquences d'opérations isolées (find then create if not found, vraisemblablement) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations

0 votes

@Discipol si vous voulez faire un ensemble d'opérations atomiques, vous devriez 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 qu'une ou deux requêtes la plupart du temps. voir : docs.mongodb.org/manual/tutorial/performance-two-phase-commits

0voto

Vidar Points 334

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 s'il n'existe pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X