2011-12-06 17 views
5

Ho una collezione molto grande su MongoDB e voglio rimuovere il record duplicato da quella raccolta. Mi viene in mente un primo pensiero: eliminare l'indice e ricostruire l'indice con dropDups. Tuttavia, i dati duplicati sono troppi per essere gestiti da MongoDB.Come rimuovere il record duplicato in MongoDB da MapReduce?

Quindi mi rivolgo a MapReduce per chiedere aiuto. Ecco i miei progressi attuali.

m = function() { 
    emit(this.myid, 1); 
} 

r = function (k, vals) { 
    return Array.sum(vals); 
} 

res = db.userList.mapReduce(m,r, { out : "myoutput" }); 

E tutti i "myid" del record duplicato sono memorizzati nella raccolta "myoutput". Tuttavia, non so come rimuovere il record da userList facendo riferimento a myoutput.myid. Si suppone di essere qualcosa di simile:

db.myoutput.find({value: {$gt: 1}}).forEach(
    function(obj) { 
     db.userList.remove(xxxxxxxxx) // I don't know how to do so 
}) 

Btw, utilizzando foreach sembra cancellerà tutti i record con il myid sano di mente. Ma voglio solo rimuovere i record duplicati. Es:

{ "_id" : ObjectId("4edc6773e206a55d1c0000d8"), "myid" : 0 } 
{ "_id" : ObjectId("4edc6780e206a55e6100011a"), "myid" : 0 } 

{ "_id" : ObjectId("4edc6784e206a55ed30000c1"), "myid" : 0 } 

Il risultato finale deve conservare solo un record. Qualcuno può darmi un aiuto su questo?

Grazie. :)

risposta

8

il più pulito è probabilmente quello di scrivere uno script lato client che elimina i record:

db.myoutput.find({value: {$gt: 1}}).forEach(
    function(obj) { 
    var cur = db.userList.find({ myid: obj._id }, {_id: 1}); 
    var first = true; 
    while (cur.hasNext()) { 
     var doc = cur.next(); 
     if (first) {first = false; continue;} 
     db.userList.remove({ _id: doc._id }); 
    } 
}) 

non ho ancora testato questo codice in modo sempre doppio controllo se si esegue con i dati prod ..

+3

Grazie. Funziona. Tuttavia, ci sarà il record 3M nella raccolta myoutput. La velocità di esecuzione è estremamente lenta. È possibile accelerarlo? –

1

Mentre la risposta sopra è abbastanza efficace, è infatti estremamente lenta se si hanno record 900K o 3M nel proprio database/collezione.

Se si tratta di grandi quantità di dati, suggerisco di prendere la lunga strada:

  • Selezionare gli elementi utilizzando un gruppo da Analog - db.collection.group()
  • Conservare questi dati utilizzando la funzione di ridurre in un array
  • Salvare i dati esportati come JSON
  • Importarlo nuovamente utilizzando mongoimport in un database pulito.

Per 900K voci, questo ha richiesto circa 35s (query di gruppo).

Implementazione in PHP:

$mongo_client = new MongoClient(); 
$collection = $mongo_client->selectCollection("main", "settings"); 

//Group by the field "code" 
$keys = array("code" => 1); 
//You must create objects for every field you wish to transfer (except the one grouped by - that gets auto-transferred) 
$initial = array("location" => "", "name" => "", "score" => 0, "type" => ""); 
//The reduce function will set the grouped properties 
$reduce = "function (obj, prev) { prev.location = obj.location; prev.name = obj.name; prev.score = obj.score; prev.type = obj.type; }"; 

$fh = fopen("Export.json", "w"); 
$unique_set = $collection->group($keys, $initial, $reduce); 
fwrite($fh, json_encode($unique_set['retval'])); 
fclose($fh); 

Se si dispone di pochissime duplicati, in esecuzione su PHP potrebbe non essere l'opzione migliore, ma il mio set ha avuto un enorme numero di duplicati, in modo che il set di dati finale è stato facile maniglia. Forse qualcuno lo troverà utile per la velocità. (e il trasferimento alla shell mongo dovrebbe essere abbastanza semplice.)

Ricorda, tuttavia, che dovrai riformattare il file finale per avere 1 documento per riga affinché funzioni con mongoimport. (Una ricerca/sostituisci tutto dovrebbe andare bene qui.)

0
/* 
* This map reduce will output a new collection: "duplicateinvoices" 
* { "_id" : "12345", "value" : 2 } 
* { "_id" : "23456", "value" : 2 } 
* ... 
**/ 
m = function() { 
    emit(this.MlsId, 1); 
} 

r = function (k, vals) { 
    return Array.sum(vals); 
} 

res = db.invoices.mapReduce(m,r, { out : "duplicateinvoices" }); 

/* 
* We have two approaches (we should test wich is faster/reliable, i didn't 
**/ 

/* OPTION 1 */ 
// We iterate over duplicateinvoices and get the media-hash 
// of the ones with value > 1 the duplicates 
db.duplicateinvoices.find({value: {$gt: 1}}).forEach(
    function(invoice) { 
     // temporary save one of this objects into a variable 
     var obj = db.invoices.findOne({ media_hash: invoice._id }); 
     // remove all media-hash matched invoices from invoice collection 
     db.invoices.remove({media_hash: invoice._id}) 
     // insert again the previously saved object into collection 
     db.invoices.insert(obj) 
    } 
) 

/* OPTION 2 */ 
// We iterate over duplicateinvoices and get the media-hash 
// of the ones with value > 1 the duplicates 
db.duplicateinvoices.find({value: {$gt: 1}}).forEach(
    function(invoice) { 
     // Invoices cursor with all the media_hash matched documents 
     var cur = db.invoices.find({ media_hash: invoice._id }); 
     var first = true; 
     while (cur.hasNext()) { 
      var doc = cur.next(); 
      // Skip the first one 
      if (first) {first = false; continue;} 
      // Delete the others matched documents 
      db.userList.remove({ _id: doc._id }); 
     } 
    } 
) 

Fonti:

How to remove duplicate record in MongoDB by MapReduce? http://openmymind.net/2011/1/20/Understanding-Map-Reduce/ http://docs.mongodb.org/manual/tutorial/map-reduce-examples/

1

in realtà non v'è alcuna necessità di MapReduce qui. che dire di questo:? incollare il codice in guscio mongo:

function removeDupls (collectionName, keyField, reportEvery) { 
    if (reportEvery === undefined) {reportEvery=10;} 
    sort = {}; 
    sort[keyField] = 1; 
    var myidLast; 
    var res = {docsCnt:0,docsRemoved:0} 
    db[collectionName].find().sort(sort).clone().forEach(
     function(doc) { 
       res['docsCnt'] += 1; 
       if (doc.myid == myidLast) {db[collectionName].remove({_id:doc._id}); res['docsRemoved'] +=1;} 
       else {myidLast = doc.myid;} 
       if (res['docsCnt'] % reportEvery === 0) {print (JSON.stringify(res))} 
      } 
    ); 
    return res; 
} 

poi chiamarlo:

removeDupls('users','myid',1000) 

questo lavoro e, probabilmente, sarà più veloce di qualsiasi MapReduce> rimuovere posti di lavoro (a seconda della quantità di documenti duplicati) Se vuoi renderlo molto veloce, devi conservare i _id dei documenti da rimuovere in una matrice temporanea, quindi utilizzare la rimozione batch.

Problemi correlati