mongodb: insérer s'il n'existe pas

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

  • je veux aussi garder une trace de la première fois que je les ai insérés, et la dernière fois que je les ai vus dans une mise à jour.
  • Je ne veux pas avoir de documents en double.
  • Je ne veux pas supprimer un document qui a déjà été enregistré, mais qui n'est pas dans ma mise à jour.
  • 95% (estimé) des enregistrements ne sont pas modifiés du jour au lendemain. 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 qu'il y a quelque chose intégré pour faire cela, mais le document pour update () est mmmhhh.... un peu laconique.... ( http://www.mongodb.org/display/DOCS/Updating )

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

105
demandé sur Michael Currie 2010-05-10 11:33:32

8 réponses

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

Cela remplace entièrement votre bloc if-find-else-update. Il insérera si la clé n'existe pas et 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 voulez écrire:

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

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

114
répondu Van Nguyen 2017-12-11 08:18:22

Depuis 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 un 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,
    )
41
répondu andy 2016-12-01 21:22:54

Vous pouvez toujours créer un index unique, ce qui entraîne le rejet par MongoDB d'une sauvegarde conflictuelle. Considérez ce qui suit en utilisant 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 }
13
répondu Ram Rajamony 2012-12-12 20:10:47

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

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
9
répondu YulCheney 2014-09-14 15:53:40

1. L'Utilisation De Mise À Jour.

Dessin 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'il est trouvé ( à partir des 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. Utiliser $set

Si vous voulez mettre à jour une sélection du document, mais pas le tout, vous pouvez utiliser la méthode $set avec update. (encore une fois, à partir des documents )... 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 permet d'éviter d'écraser accidentellement tous vos documents avec { name: 'jason borne' }.

6
répondu Meshach Jackson 2012-05-25 16:25:58

Je ne pense pas que mongodb supporte ce type de upserting sélectif. J'ai le même problème que LeMiz, et l'utilisation de update (criteria, newObj, upsert, multi) ne fonctionne pas correctement lorsqu'il s'agit à la fois d'un horodatage 'créé' et 'mis à jour'. Compte tenu de la déclaration upsert suivante:

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

Scénario # 1-document avec' nom 'de' abc ' n'existe pas: Un nouveau document est créé avec 'nom' = 'abc', 'created' = 2010-07-14 11:11:11, et 'mis à jour' = 2010-07-14 11:11:11.

Scénario # 2 - document avec ' nom ' de ' abc ' existe déjà avec ce qui suit: 'name' = 'abc', 'created' = 2010-07-12 09:09:09 et 'mis à jour' = 2010-07-13 10:10:10. Après l'upsert, le document serait maintenant le même que le résultat dans le scénario # 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 en cas de mise à jour.

Ma solution était de créer un index unique sur les champs critera , d'effectuer un insert et immédiatement après d'effectuer un mettre à jour juste sur le champ 'mis à jour'.

5
répondu Yonsink 2010-07-15 21:38:21

Résumé

  • Vous avez une collection d'enregistrements existante.
  • vous avez un ensemble d'enregistrements qui contiennent des mises à jour des enregistrements existants.
  • certaines des mises à jour ne mettent pas vraiment à jour quoi que ce soit, elles dupliquent ce que vous avez déjà.
  • toutes les mises à jour contiennent les mêmes champs qui sont déjà là, juste é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é.

Remarque, je présume PyMongo, changer en fonction de votre langue de choix.

Instructions:

  1. Créez la collection avec un index avec unique=true afin de ne pas obtenir d'enregistrements en double.

  2. Parcourez vos enregistrements d'entrée, en créant des lots d'environ 15 000 enregistrements. Pour chaque enregistrement du lot, créez un dict composé des données que vous souhaitez insérer, en supposant que chacun sera un nouvel enregistrement. Ajoutez les horodatages 'créés' et 'mis à jour' à ceux-ci. Question ceci en tant que commande d'insertion par lots avec le drapeau' ContinueOnError ' =true, donc l'insertion de tout le reste se produit même s'il y a une clé en double (ce qui semble être le cas). CELA VA ARRIVER TRÈS VITE. Inserts en vrac rock, j'ai obtenu des niveaux de performance 15k/second. D'autres notes sur ContinueOnError, voir http://docs.mongodb.org/manual/core/write-operations/

    Les insertions D'enregistrement se produisent très rapidement, de sorte que vous aurez terminé avec ces insertions en un rien de temps. Maintenant, il est temps de mettre à jour les enregistrements pertinents. Pour ce faire, avec une récupération par lots, beaucoup plus rapide qu'un à la fois.

  3. Parcourez à nouveau tous vos enregistrements d'entrée, en créant des lots d'environ 15K. Extraire les clés (mieux s'il y a une clé, mais ne peut pas être aidé s'il n'y en a pas). Récupérer ce tas d'enregistrements de Mongo avec une base de données.collectionNameBlah.trouver ({champ: {$dans: [1, 2,3 ...}) requête. Pour chacun de ces enregistrements, déterminez s'il y a une mise à jour et, le cas échéant, lancez la mise à jour, y compris mise à jour de l'horodatage 'mis à jour'.

    Malheureusement, nous devrions noter, MongoDB 2.4 et ci-dessous n'incluent pas une opération de mise à jour en bloc. Ils sont en train de travailler sur ce.

Points Clés D'Optimisation:

  • Les inserts accéléreront considérablement vos opérations en vrac.
  • récupérer des enregistrements en masse accélérera également les choses.
  • les mises à jour individuelles sont la seule route possible maintenant, mais 10Gen y travaille. Vraisemblablement, ce sera dans 2.6, cependant Je ne suis pas sûr que ce sera fini d'ici là, il y a beaucoup de choses à faire (j'ai suivi leur système Jira).
5
répondu Kevin J. Rice 2013-06-09 16:55:28

En général, l'utilisation de update est meilleure dans MongoDB car elle va simplement créer le document s'il n'existe pas encore, bien que je ne sache pas comment travailler cela avec votre adaptateur python.

Deuxièmement, si vous avez seulement besoin de savoir si ce document existe ou non, count() qui ne renvoie qu'un nombre sera une meilleure option que find_one qui soi-disant transfère le document entier de votre MongoDB provoquant un trafic inutile.

4
répondu Thomas R. Koll 2012-03-20 03:41:31