web-dev-qa-db-fra.com

mongodb: insère s'il n'existe pas

Chaque jour, je reçois un stock de documents (une mise à jour). Ce que je veux faire, c'est insérer chaque élément qui n'existe pas déjà.

  • Je souhaite également garder une trace de la première fois que je les ai insérés et de la dernière fois que je les ai vus dans une mise à jour.
  • Je ne veux pas avoir des documents en double.
  • Je ne souhaite pas supprimer un document précédemment enregistré, mais qui ne figure pas dans ma mise à jour.
  • 95% (estimation) des enregistrements ne sont pas modifiés de jour en jour.

J'utilise le pilote Python (pymongo).

Ce que je fais actuellement est (pseudo-code):

for each document in update:
      existing_document = collection.find_one(document)
      if not existing_document:
           document['insertion_date'] = now
      else:
           document = existing_document
      document['last_update_date'] = now
      my_collection.save(document)

Mon problème est qu’il est très lent (40 minutes pour moins de 100 000 enregistrements, et j’en ai des millions dans la mise à jour). Je suis à peu près sûr que quelque chose est prévu pour faire cela, mais le document pour update () est mmmhhh .... un peu succinct .... ( http://www.mongodb.org/display/DOCS/Mise à jour )

Quelqu'un peut-il conseiller comment le faire plus rapidement?

121
LeMiz

On dirait que vous voulez faire un "upsert". MongoDB a un support intégré pour cela. Passez un paramètre supplémentaire à votre appel à update (): {upsert: true}. Par exemple:

key = {'key':'value'}
data = {'key2':'value2', 'key3':'value3'};
coll.update(key, data, upsert=True); #In python upsert must be passed as a keyword argument

Ceci remplace entièrement votre bloc if-find-else-update. Il insérera si la clé n'existe pas et se mettra à jour si c'est le cas.

Avant:

{"key":"value", "key2":"Ohai."}

Après:

{"key":"value", "key2":"value2", "key3":"value3"}

Vous pouvez également spécifier les données que vous souhaitez écrire:

data = {"$set":{"key2":"value2"}}

Maintenant, votre document sélectionné mettra à jour la valeur de "key2" uniquement et laissera tout le reste intact.

129
Van Nguyen

A partir de MongoDB 2.4, vous pouvez utiliser $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )

Définissez 'insertion_date' en utilisant $ setOnInsert et 'last_update_date' en utilisant $ set dans votre commande upsert.

Pour transformer votre pseudocode en exemple de travail:

now = datetime.utcnow()
for document in update:
    collection.update_one(
        {"_id": document["_id"]},
        {
            "$setOnInsert": {"insertion_date": now},
            "$set": {"last_update_date": now},
        },
        upsert=True,
    )
52
andy

Vous pouvez toujours créer un index unique, ce qui oblige MongoDB à rejeter une sauvegarde en conflit. Considérez ce qui suit avec le shell mongodb:

> db.getCollection("test").insert ({a:1, b:2, c:3})
> db.getCollection("test").find()
{ "_id" : ObjectId("50c8e35adde18a44f284e7ac"), "a" : 1, "b" : 2, "c" : 3 }
> db.getCollection("test").ensureIndex ({"a" : 1}, {unique: true})
> db.getCollection("test").insert({a:2, b:12, c:13})      # This works
> db.getCollection("test").insert({a:1, b:12, c:13})      # This fails
E11000 duplicate key error index: foo.test.$a_1  dup key: { : 1.0 }
15
Ram Rajamony

Vous pouvez utiliser Upsert avec l'opérateur $ setOnInsert.

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
11
YulCheney

1. Utilisez la mise à jour.

S'inspirant de la réponse de Van Nguyen ci-dessus, utilisez update au lieu de save. Cela vous donne accès à l'option upsert.

[~ # ~] note [~ # ~] : cette méthode remplace le document entier lorsqu'elle est trouvée ( parmi les documents ).

var conditions = { name: 'borne' }   , update = { $inc: { visits: 1 }} , options = { multi: true };

Model.update(conditions, update, options, callback);

function callback (err, numAffected) {   // numAffected is the number of updated documents })

1.a. Utilisez $ set

Si vous souhaitez mettre à jour une sélection du document, mais pas le tout, vous pouvez utiliser la méthode $ set avec update. (encore une fois, de la documentation ) ... Donc, si vous voulez définir ...

var query = { name: 'borne' };  Model.update(query, ***{ name: 'jason borne' }***, options, callback)

Envoyez le comme ...

Model.update(query, ***{ $set: { name: 'jason borne' }}***, options, callback)

Cela évite d’écraser accidentellement l’ensemble de vos documents avec { name: 'jason borne' }.

6
Meshach Jackson

Je ne pense pas que mongodb soutienne ce type de upserting sélectif. J'ai le même problème que LeMiz, et utiliser pdate (critères, newObj, upsert, multi) ne fonctionne pas correctement avec un horodatage 'créé' et 'mis à jour'. Compte tenu de la déclaration suivante UPS:

update( { "name": "abc" }, 
        { $set: { "created": "2010-07-14 11:11:11", 
                  "updated": "2010-07-14 11:11:11" }},
        true, true ) 

Scénario n ° 1 - le document avec 'nom' de 'abc' n'existe pas: un nouveau document est créé avec 'nom' = 'abc', 'created' = 2010-07-14 11:11:11 et 'updated' = 2010-07-14 11:11:11.

Scénario n ° 2 - le document avec 'nom' de 'abc' existe déjà avec les éléments suivants: 'name' = 'abc', 'created' = 2010-07-12 09:09:09 et 'updated' = 2010-07 -13 10:10:10. Après la conversion, le document serait maintenant identique au résultat du scénario n ° 1. Il n'y a aucun moyen de spécifier dans un upsert quels champs doivent être définis en cas d'insertion et quels champs doivent être laissés seuls lors d'une mise à jour.

Ma solution consistait à créer un index unique sur les champs critère, à effectuer une insertion et à effectuer ensuite une mise à jour uniquement sur le champ "mis à jour".

5
Yonsink

Résumé

  • Vous avez une collection existante d'enregistrements.
  • Vous avez un ensemble d'enregistrements contenant des mises à jour des enregistrements existants.
  • Certaines mises à jour ne mettent vraiment rien à jour, elles dupliquent ce que vous avez déjà.
  • Toutes les mises à jour contiennent les mêmes champs que ceux déjà présents, mais éventuellement des valeurs différentes.
  • Vous voulez suivre quand un enregistrement a été modifié pour la dernière fois, où une valeur a réellement changé.

Notez que je présume que PyMongo, changez-le en fonction de la langue de votre choix.

Instructions:

  1. Créez la collection avec un index unique = true afin d'éviter les doublons.

  2. Parcourez vos enregistrements d’entrée en créant des lots de 15 000 enregistrements environ. Pour chaque enregistrement du lot, créez un dict contenant les données que vous souhaitez insérer, en supposant que chaque enregistrement sera un nouvel enregistrement. Ajoutez les horodatages "créé" et "mis à jour" à ceux-ci. Émettez ceci en tant que commande d'insertion par lot avec l'indicateur 'ContinueOnError' = = true, de sorte que l'insertion de tout le reste se produise même s'il y a une clé dupliquée (ce qui semble être le cas). CELA ARRIVERA TRES RAPIDEMENT. Bulk inserts rock, j'ai atteint les 15k/seconde de performance. Des notes complémentaires sur ContinueOnError, voir http://docs.mongodb.org/manual/core/write-operations/

    Les insertions de disques arrivent TRES rapidement, vous aurez donc fini avec ces insertions en un rien de temps. Maintenant, il est temps de mettre à jour les enregistrements pertinents. Faites cela avec une récupération par lot, beaucoup plus rapide qu'un à la fois.

  3. Parcourez à nouveau tous vos enregistrements d’entrée en créant des lots de 15 Ko environ. Extrayez les clés (mieux s’il ya une clé, mais ne peut pas vous aider si ce n’est pas le cas). Récupérez ce lot d'enregistrements de Mongo avec une requête db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ...}). Pour chacun de ces enregistrements, déterminez s'il existe une mise à jour et, le cas échéant, lancez la mise à jour, y compris en mettant à jour l'horodatage "mis à jour".

    Malheureusement, il convient de noter que MongoDB 2.4 et versions antérieures n'incluent pas une opération de mise à jour en masse. Ils y travaillent.

Points d'optimisation clés:

  • Les inserts vont considérablement accélérer vos opérations en vrac.
  • Récupérer des documents en masse accélérera les choses aussi.
  • Les mises à jour individuelles sont désormais le seul itinéraire possible, mais 10Gen y travaille. Vraisemblablement, cela sera dans la version 2.6, bien que je ne sois pas sûr que ce soit fini à ce moment-là, il y a beaucoup de choses à faire (j'ai suivi leur système Jira).
5
Kevin J. Rice

En général, utiliser update est préférable dans MongoDB, car il ne créera le document que s’il n’existe pas encore, bien que je ne sois pas sûr de l’utiliser avec votre adaptateur python.

Deuxièmement, si vous avez seulement besoin de savoir si ce document existe ou non, count (), qui renvoie uniquement un nombre, sera une meilleure option que find_one, qui est supposé transférer l'intégralité du document de votre MongoDB, générant ainsi un trafic inutile.

4
Thomas R. Koll