2011-12-06 29 views
5

Tengo una colección muy grande en MongoDB y quiero eliminar el registro duplicado de esa colección. Lo primero que se me viene a la mente es bajar el índice y reconstruir el índice con dropDups. Sin embargo, los datos duplicados son demasiados para ser manejados por MongoDB.¿Cómo eliminar registros duplicados en MongoDB de MapReduce?

Así que me dirijo a MapReduce para obtener ayuda. Aquí está mi progreso actual.

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

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

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

Y todos los "duplicados" del registro duplicado se almacenan en la colección "myoutput". Sin embargo, no sé cómo eliminar el registro de UserList haciendo referencia a myoutput.myid. Se supone que ser algo como esto:

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

Por cierto, usando foreach parece borrará todos los registros con el myid cuerdo. Pero solo quiero eliminar registros duplicados. Ejemplo:

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

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

El resultado final debe conservar solo un registro. ¿Alguien puede ayudarme con esto?

Gracias. :)

Respuesta

8

el más limpio es probablemente para escribir un script del lado del cliente que elimina los registros:

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 }); 
    } 
}) 

No he probado este código por lo que siempre vuelva a comprobar si se ejecuta con los datos de prod ..

+3

Gracias. Funciona. Sin embargo, habrá un registro de 3M en la colección myoutput. La velocidad de ejecución es extremadamente lenta. ¿Es posible acelerarlo? –

1

Mientras la respuesta anterior es bastante efectiva, de hecho es extremadamente lenta si tiene registros 900K o 3M en su base de datos/colección.

Si se trata de grandes cantidades de datos, sugiero tomar el largo camino:

  • Seleccione los elementos que utilizan un GROUP BY analógico - db.collection.group()
  • almacenar estos datos utilizando la función de reducir en una matriz
  • Guardar datos exportados como JSON
  • Importación de nuevo usando mongoimport en una base de datos limpia.

Para 900K entradas, esto tomó alrededor de 35 s (consulta de grupo).

implementación en 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); 

Si tiene muy pocos duplicados, ejecutarlo en PHP podría no ser la mejor opción, pero mi juego tuvo un enorme número de duplicados, por lo que el conjunto de datos final fue fácil encargarse de. Quizás alguien encuentre útil esto para la velocidad. (y la transferencia a mongo shell debería ser bastante fácil.)

Recuerde, sin embargo, que tendrá que volver a formatear el archivo final para tener 1 documento por línea para que funcione con mongoimport. (Una búsqueda/reemplazo todo debería estar bien aquí.)

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 }); 
     } 
    } 
) 

Fuentes:

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

en realidad no hay necesidad de mapreduce aquí. ¿qué tal esto? pegar código en consola 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; 
} 

luego llamarlo:

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

esto va a funcionar y, probablemente, será más rápido que cualquier mapreduce> eliminar la tarea (dependiendo de la cantidad de documentos duplicados) Si desea hacerlo realmente rápido, debe almacenar los _ids de los documentos que se eliminarán en una matriz temporal y luego usar la eliminación por lotes.

Cuestiones relacionadas