mongodb: insertar si no existe


Cada día, recibo un stock de documentos (una actualización). Lo que quiero hacer es insertar cada elemento que no existe ya.

  • También quiero hacer un seguimiento de la primera vez que los inserté, y la última vez que los vi en una actualización.
  • No quiero tener documentos duplicados.
  • No quiero eliminar un documento que haya sido guardado previamente, pero que no esté en mi actualización.
  • El 95% (estimado) de los registros no están modificados desde el día hasta dia.

Estoy usando el controlador Python (pymongo).

Lo que hago actualmente es (pseudo-código):

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)

Mi problema es que es muy lento (40 minutos para menos de 100 000 registros, y tengo millones de ellos en la actualización). Estoy bastante seguro de que hay algo incorporado para hacer esto, pero el documento para update () es mmmhhh.... un poco conciso.... ( http://www.mongodb.org/display/DOCS/Updating )

¿Puede alguien aconsejar cómo hacerlo más rápido?

Author: Michael Currie, 2010-05-10

8 answers

Parece que quieres hacer un "upsert". MongoDB tiene soporte incorporado para esto. Pasa un parámetro adicional a tu llamada update (): {upsert:true}. Por ejemplo:

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

Esto reemplaza su bloque if-find-else-update por completo. Se insertará si la clave no existe, y se actualizará si lo hace.

Antes:

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

Después de:

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

También puede especificar qué datos desea escribir:

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

Ahora el documento seleccionado actualizará el valor de " key2" sólo y dejar todo lo demás intacto.

 114
Author: Van Nguyen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-11 08:18:22

A partir de MongoDB 2.4, puede usar set setOnInsert (http://docs.mongodb.org/manual/reference/operator/setOnInsert/)

Establece 'insertion_date' usando set setOnInsert y 'last_update_date' usando $set en tu comando upsert.

Para convertir tu pseudocódigo en un ejemplo de trabajo:

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
Author: andy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-01 21:22:54

Siempre se puede crear un índice único, lo que hace que MongoDB rechace un guardado conflictivo. Considere lo siguiente usando mongodb shell:

> 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
Author: Ram Rajamony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-12 20:10:47

Puede usar Upsert con el operador set setOnInsert.

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
 9
Author: YulCheney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-14 15:53:40

1. Usa Actualización.

Usando la respuesta de Van Nguyen anterior, usa update en lugar de save. Esto le da acceso a la opción upsert.

NOTA : Este método anula todo el documento cuando se encuentra (Desde los documentos)

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. Use {set

Si desea actualizar una selección del documento, pero no todo, puede usar el método set set con update. (de nuevo, De los documentos)... Así que, si quieres establecer...

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

Envíalo como...

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

Esto ayuda a evitar sobrescribir accidentalmente todos sus documentos con { name: 'jason borne' }.

 6
Author: Meshach Jackson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-25 16:25:58

No creo que mongodb soporte este tipo de upserting selectivo. Tengo el mismo problema que LeMiz, y usar update(criteria, newObj, upsert, multi) no funciona bien cuando se trata de una marca de tiempo 'creada' y 'actualizada'. Dada la siguiente declaración de upsert:

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

Escenario # 1-el documento con' nombre 'de' abc ' no existe: El nuevo documento se crea con 'name' = 'abc', 'created' = 2010-07-14 11:11:11, y 'updated' = 2010-07-14 11: 11: 11.

Escenario # 2 - el documento con' nombre 'de' abc ' ya existe con lo siguiente: 'name' = 'abc', 'created' = 2010-07-12 09:09:09, and 'updated' = 2010-07-13 10: 10: 10. Después del upsert, el documento ahora sería el mismo que el resultado en el escenario #1. No hay manera de especificar en un upsert qué campos se configurarán si se insertan, y qué campos se dejarán solos si se actualizan.

Mi solución fue crear un índice único en los campos critera, realizar un insert e inmediatamente después realizar un actualizar solo en el campo' actualizado'.

 5
Author: Yonsink,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-15 21:38:21

Resumen

  • Usted tiene una colección existente de registros.
  • Tiene un conjunto de registros que contienen actualizaciones de los registros existentes.
  • Algunas de las actualizaciones realmente no actualizan nada, duplican lo que ya tienes.
  • Todas las actualizaciones contienen los mismos campos que ya existen, solo valores posiblemente diferentes.
  • Desea realizar un seguimiento de cuándo se cambió por última vez un registro, dónde cambió realmente un valor.

Nota, estoy presumiendo PyMongo, cambie para adaptarse a su idioma de elección.

Instrucciones:

  1. Cree la colección con un índice con unique = true para que no obtenga registros duplicados.

  2. Itere sobre sus registros de entrada, creando lotes de ellos de 15,000 registros aproximadamente. Para cada registro en el lote, cree un dict que consiste en los datos que desea insertar, suponiendo que cada uno va a ser un nuevo registro. Agregue las marcas de tiempo' creado 'y' actualizado ' a estos. Cuestión esto como un comando de inserción por lotes con la bandera 'ContinueOnError' =true, por lo que la inserción de todo lo demás sucede incluso si hay una clave duplicada allí (que suena como que habrá). ESTO SUCEDERÁ MUY RÁPIDO. Insertos a granel rock, he conseguido 15k / segundo niveles de rendimiento. Más notas sobre ContinueOnError, ver http://docs.mongodb.org/manual/core/write-operations/

    Las inserciones de registro ocurren MUY rápido, por lo que terminarás con esas inserciones en poco tiempo. Ahora, es el momento de actualizar los registros pertinentes. Haga esto con una recuperación por lotes, mucho más rápido que uno a la vez.

  3. Itere sobre todos sus registros de entrada de nuevo, creando lotes de 15K más o menos. Extraiga las claves (mejor si hay una clave, pero no se puede evitar si no la hay). Recuperar este montón de registros de Mongo con una base de datos.collectionNameBlah.buscar ({campo: {in en: [1, 2,3 ...}) consulta. Para cada uno de estos registros, determine si hay una actualización, y si es así, emita la actualización, incluyendo actualizando la marca de tiempo 'actualizada'.

    Desafortunadamente, debemos tener en cuenta que MongoDB 2.4 y versiones posteriores NO incluyen una operación de actualización masiva. Están trabajando en eso.

Puntos Clave de Optimización:

  • Los insertos acelerarán enormemente sus operaciones a granel.
  • Recuperar registros en masa también acelerará las cosas.
  • Las actualizaciones individuales son la única ruta posible ahora, pero 10Gen está trabajando en ello. Presumiblemente, esto será en 2.6, aunque No estoy seguro de si estará terminado para entonces, hay un montón de cosas que hacer (he estado siguiendo su sistema Jira).
 5
Author: Kevin J. Rice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-09 16:55:28

En general, usar update es mejor en MongoDB, ya que solo creará el documento si aún no existe, aunque no estoy seguro de cómo trabajar con su adaptador python.

En segundo lugar, si solo necesita saber si ese documento existe o no, count() que devuelve solo un número será una mejor opción que find_one que supuestamente transfiere todo el documento desde su MongoDB causando tráfico innecesario.

 4
Author: Thomas R. Koll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-20 03:41:31