mongodb: inserisci se non esiste


146

Ogni giorno ricevo una scorta di documenti (un aggiornamento). Quello che voglio fare è inserire ogni elemento che non esiste già.

  • Voglio anche tenere traccia della prima volta che li ho inseriti e l'ultima volta che li ho visti in un aggiornamento.
  • Non voglio avere documenti duplicati.
  • Non voglio rimuovere un documento che è stato precedentemente salvato, ma non è nel mio aggiornamento.
  • Il 95% (stimato) dei record non è modificato di giorno in giorno.

Sto usando il driver Python (pymongo).

Quello che faccio attualmente è (pseudo-codice):

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)

Il mio problema è che è molto lento (40 minuti per meno di 100000 registrazioni e ne ho milioni nell'aggiornamento). Sono abbastanza sicuro che ci sia qualcosa incorporato per farlo, ma il documento per update () è mmmhhh .... un po 'conciso .... ( http://www.mongodb.org/display/DOCS/Updating )

Qualcuno può consigliare come farlo più velocemente?

Risposte:


153

Sembra che tu voglia fare un "upsert". MongoDB ha il supporto integrato per questo. Passa un parametro aggiuntivo alla chiamata update (): {upsert: true}. Per esempio:

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

Questo sostituisce del tutto il blocco if-find-else-update. Inserirà se la chiave non esiste e si aggiornerà in caso affermativo.

Prima:

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

Dopo:

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

Puoi anche specificare quali dati vuoi scrivere:

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

Ora il documento selezionato aggiorna solo il valore di "key2" e lascia tutto intatto.


5
Questo è quasi quello che voglio! Come posso non toccare il campo insertion_date se l'oggetto è già presente?
LeMiz,

24
puoi per favore dare un esempio di come impostare un campo al primo inserimento e non aggiornarlo se esiste? @VanNguyen
Ali Shakiba

7
La prima parte della tua risposta è sbagliata, credo. coll.update sostituirà i dati a meno che tu non usi $ set. Quindi After sarà effettivamente: {'key2': 'value2', 'key3': 'value3'}
James Blackburn,

9
-1 Questa risposta è pericolosa. Trovi per il valore di "chiave" e poi cancelli "chiave", in modo che successivamente non potrai ritrovarla. Questo è un caso d'uso molto improbabile.
Mark E. Haase,

23
Dovresti usare l'operatore $ setOnInsert! Upsert aggiorna anche il documento se trova la query.
YulCheney,

64

A partire da MongoDB 2.4, puoi utilizzare $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )

Impostare 'insertion_date' usando $ setOnInsert e 'last_update_date' usando $ set nel comando upsert.

Per trasformare il tuo pseudocodice in un esempio funzionante:

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

3
Questo è corretto, puoi verificare la presenza di un documento corrispondente a un filtro e inserire qualcosa se non lo trovi, usando $ setOnInsert. Si noti tuttavia che c'era un bug in cui non era possibile $ setOnInsert con il campo _id: si direbbe qualcosa del tipo "impossibile modificare il campo _id". Questo era un bug, corretto in v2.5.4 o altro. Se vedi questo messaggio o problema, procurati la versione più recente.
Kieren Johnstone,

19

Puoi sempre creare un indice univoco, il che fa sì che MongoDB rifiuti un salvataggio in conflitto. Considera quanto segue usando la 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 }


6

1. Usa Aggiorna.

Attingendo alla risposta di Van Nguyen sopra, usa l'aggiornamento invece di salvare. Questo ti dà accesso all'opzione upsert.

NOTA : questo metodo sovrascrive l'intero documento quando viene trovato ( dai documenti )

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. Usa $ set

Se vuoi aggiornare una selezione del documento, ma non il tutto, puoi usare il metodo $ set con update. (di nuovo, dai documenti ) ... Quindi, se si desidera impostare ...

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

Invia come ...

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

Questo aiuta a prevenire la sovrascrittura accidentale di tutti i documenti con { name: 'jason borne' }.


6

Sommario

  • Hai una raccolta di record esistente.
  • Hai un set di record che contiene aggiornamenti ai record esistenti.
  • Alcuni degli aggiornamenti non aggiornano davvero nulla, duplicano ciò che hai già.
  • Tutti gli aggiornamenti contengono gli stessi campi che sono già presenti, possibilmente solo valori diversi.
  • Si desidera tenere traccia dell'ultima modifica di un record, in cui è stato effettivamente modificato un valore.

Nota, presumo PyMongo, cambia in base alla tua lingua preferita.

Istruzioni:

  1. Crea la raccolta con un indice con unique = true in modo da non ottenere record duplicati.

  2. Scorri i tuoi record di input, creando lotti di essi di circa 15.000 record. Per ogni record nel batch, creare un dict costituito dai dati che si desidera inserire, presumendo che ognuno sarà un nuovo record. Aggiungi i timestamp "creati" e "aggiornati" a questi. Emettilo come comando di inserimento batch con il flag 'ContinueOnError' = true, quindi l'inserimento di tutto il resto avviene anche se c'è una chiave duplicata (che sembra che ci sarà). Questo accadrà molto velocemente. Inserti di massa rock, ho ottenuto livelli di prestazione di 15k / secondo. Ulteriori note su ContinueOnError, vedi http://docs.mongodb.org/manual/core/write-operations/

    Gli inserti dei record avvengono MOLTO velocemente, quindi avrai finito con quegli inserti in pochissimo tempo. Ora è il momento di aggiornare i record pertinenti. Fallo con un recupero batch, molto più veloce di uno alla volta.

  3. Scorrere nuovamente su tutti i record di input, creando batch di circa 15 KB. Estrai le chiavi (meglio se c'è una chiave, ma non può essere aiutata se non c'è). Recupera questo gruppo di record da Mongo con una query db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ...}). Per ciascuno di questi record, determinare se esiste un aggiornamento e, in tal caso, emettere l'aggiornamento, incluso l'aggiornamento del timestamp "aggiornato".

    Sfortunatamente, dovremmo notare che MongoDB 2.4 e precedenti NON includono un'operazione di aggiornamento collettivo. Ci stanno lavorando.

Punti chiave di ottimizzazione:

  • Gli inserti velocizzeranno notevolmente le operazioni in blocco.
  • Il recupero di record in massa accelererà anche le cose.
  • Gli aggiornamenti individuali sono l'unica strada possibile ora, ma 10Gen ci sta lavorando. Presumibilmente, questo sarà in 2.6, anche se non sono sicuro che sarà finito entro allora, ci sono molte cose da fare (ho seguito il loro sistema Jira).

5

Non credo che mongodb supporti questo tipo di potenziamento selettivo. Ho lo stesso problema di LeMiz e l'utilizzo dell'aggiornamento (criteri, newObj, upsert, multi) non funziona correttamente quando si ha a che fare sia con un timestamp "creato" che "aggiornato". Data la seguente dichiarazione di upsert:

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

Scenario n. 1: il documento con "nome" di "abc" non esiste: il nuovo documento viene creato con "nome" = "abc", "creato" = 14/07/2010 11:11:11 e "aggiornato" = 14/07/2010 11:11:11.

Scenario n. 2: il documento con "nome" di "abc" esiste già con quanto segue: "nome" = "abc", "creato" = 2010-07-12 09:09:09 e "aggiornato" = 2010-07 -13 10:10:10. Dopo il upsert, il documento sarebbe ora lo stesso del risultato nello scenario n. 1. Non c'è modo di specificare in un upsert quali campi impostare se inserire e quali campi lasciare soli se si aggiorna.

La mia soluzione era quella di creare un indice univoco sui campi dei criteri , eseguire un inserimento e subito dopo eseguire un aggiornamento proprio sul campo "aggiornato".


4

In generale, usare l'aggiornamento è meglio in MongoDB in quanto creerà il documento se non esiste ancora, anche se non sono sicuro di come farlo con l'adattatore python.

In secondo luogo, se hai solo bisogno di sapere se quel documento esiste o meno, count () che restituisce solo un numero sarà un'opzione migliore di find_one che presumibilmente trasferisce l'intero documento da MongoDB causando traffico non necessario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.