Moyen le plus rapide pour supprimer les documents en double dans mongodb

J'ai environ 1.7 m documents dans mongodb (dans le futur 10m+). Certains d'entre eux représentent une entrée en double que je ne veux pas. La Structure du document est quelque chose comme ceci:

{
    _id: 14124412,
    nodes: [
        12345,
        54321
        ],
    name: "Some beauty"
}

Le Document est dupliqué s'il a à au moins un nœud même qu'un autre document avec même nom. Quel est le moyen le plus rapide de supprimer les doublons?

29
demandé sur Somnath Muluk 2013-01-06 20:23:20

6 réponses

En supposant que vous voulez supprimer définitivement les documents qui contiennent un doublon name + nodes entrée de la collection, vous pouvez ajouter un index unique avec le dropDups: true option:

db.test.ensureIndex({name: 1, nodes: 1}, {unique: true, dropDups: true}) 

Comme le disent les docs, faites preuve d'une extrême prudence car cela supprimera les données de votre base de données. Sauvegardez d'abord votre base de données au cas où elle ne ferait pas exactement ce que vous attendez.

Mise à JOUR

Cette solution n'est valide que via MongoDB 2.x Car l'option dropDups n'est plus disponible dans 3.0 (docs ).

41
répondu JohnnyHK 2015-04-26 00:59:11

dropDups: true l'option n'est pas disponible dans la version 3.0.

J'ai une solution avec un cadre d'agrégation pour collecter les doublons puis les supprimer en une seule fois.

Il pourrait être un peu plus lent que les changements d ' "index" au niveau du système. Mais il est bon de considérer la façon dont vous voulez supprimer les documents en double.

A. supprimer tous les documents en une seule fois

var duplicates = [];

db.collectionName.aggregate([
  { $match: { 
    name: { "$ne": '' }  // discard selection criteria
  }},
  { $group: { 
    _id: { name: "$name"}, // can be grouped on multiple properties 
    dups: { "$addToSet": "$_id" }, 
    count: { "$sum": 1 } 
  }}, 
  { $match: { 
    count: { "$gt": 1 }    // Duplicates considered as count greater than one
  }}
],
{allowDiskUse: true}       // For faster processing if set is larger
)               // You can display result until this and check duplicates 
.forEach(function(doc) {
    doc.dups.shift();      // First element skipped for deleting
    doc.dups.forEach( function(dupId){ 
        duplicates.push(dupId);   // Getting all duplicate ids
        }
    )    
})

// If you want to Check all "_id" which you are deleting else print statement not needed
printjson(duplicates);     

// Remove all duplicates in one go    
db.collectionName.remove({_id:{$in:duplicates}})  

B. Vous pouvez supprimer des documents un par un.

db.collectionName.aggregate([
  // discard selection criteria, You can remove "$match" section if you want
  { $match: { 
    source_references.key: { "$ne": '' }  
  }},
  { $group: { 
    _id: { source_references.key: "$source_references.key"}, // can be grouped on multiple properties 
    dups: { "$addToSet": "$_id" }, 
    count: { "$sum": 1 } 
  }}, 
  { $match: { 
    count: { "$gt": 1 }    // Duplicates considered as count greater than one
  }}
],
{allowDiskUse: true}       // For faster processing if set is larger
)               // You can display result until this and check duplicates 
.forEach(function(doc) {
    doc.dups.shift();      // First element skipped for deleting
    db.collectionName.remove({_id : {$in: doc.dups }});  // Delete remaining duplicates
})
50
répondu Somnath Muluk 2016-10-01 07:28:28

Créer un vidage de collection avec mongodump

Effacer la collection

Ajouter un index unique

Restaurer la collection avec mongorestore

17
répondu dhythhsba 2016-07-01 06:42:53

J'ai trouvé cette solution qui fonctionne avec MongoDB 3.4: Je suppose que le champ avec des doublons s'appelle fieldX

db.collection.aggregate([
{
    // only match documents that have this field
    // you can omit this stage if you don't have missing fieldX
    $match: {"fieldX": {$nin:[null]}}  
},
{
    $group: { "_id": "$fieldX", "doc" : {"$first": "$$ROOT"}}
},
{
    $replaceRoot: { "newRoot": "$doc"}
}
],
{allowDiskUse:true})

Étant nouveau sur mongoDB, j'ai passé beaucoup de temps et utilisé d'autres solutions longues pour trouver et supprimer des doublons. Cependant, je pense que cette solution est soignée et facile à comprendre.

Cela fonctionne en faisant d'abord correspondre les documents contenant fieldX (j'avais des documents sans ce champ, et j'ai obtenu un résultat vide supplémentaire).

L'étape suivante regroupe les documents par fieldX, et insère uniquement le document $first dans chaque groupe en utilisant $ $ ROOT . Enfin, il remplace l'ensemble du groupe agrégé par le document trouvé en utilisant $first et $ $ ROOT.

J'ai dû ajouter allowDiskUse parce que ma collection est grande.

Vous pouvez ajouter ceci après n'importe quel nombre de pipelines, et bien que la documentation de $first mentionne une étape de tri avant d'utiliser $first, cela a fonctionné pour moi sans elle. "ne pouvait pas poster un lien ici, ma réputation est moins plus de 10 : ("

Vous pouvez enregistrer les résultats dans une nouvelle collection en ajoutant une étape $ out...

Alternativement , si l'on ne s'intéresse qu'à quelques champs, par exemple field1, field2, et non à l'ensemble du document, dans la phase de groupe sans replaceRoot:

db.collection.aggregate([
{
    // only match documents that have this field
    $match: {"fieldX": {$nin:[null]}}  
},
{
    $group: { "_id": "$fieldX", "field1": {"$first": "$$ROOT.field1"}, "field2": { "$first": "$field2" }}
}
],
{allowDiskUse:true})
6
répondu Ali Abul Hawa 2017-06-13 13:13:33
  1. Idée Générale est d'utiliser findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ pour récupérer un id aléatoire à partir des enregistrements en double dans la collection.

  2. Supprimez tous les enregistrements de la collection autres que l'ID aléatoire que nous avons récupéré de l'option findOne.

Vous pouvez faire quelque chose comme ça si vous essayez de le faire à pymongo.

def _run_query():

        try:

            for record in (aggregate_based_on_field(collection)):
                if not record:
                    continue
                _logger.info("Working on Record %s", record)

                try:
                    retain = db.collection.find_one(find_one({'fie1d1': 'x',  'field2':'y'}, {'_id': 1}))
                    _logger.info("_id to retain from duplicates %s", retain['_id'])

                    db.collection.remove({'fie1d1': 'x',  'field2':'y', '_id': {'$ne': retain['_id']}})

                except Exception as ex:
                    _logger.error(" Error when retaining the record :%s Exception: %s", x, str(ex))

        except Exception as e:
            _logger.error("Mongo error when deleting duplicates %s", str(e))


def aggregate_based_on_field(collection):
    return collection.aggregate([{'$group' : {'_id': "$fieldX"}}])

De la coquille:

  1. Remplacez find_one par findOne
  2. La même commande de suppression devrait fonctionner.
0
répondu amateur 2017-11-30 01:56:04

Voici une façon un peu plus "manuelle" de le faire:

Essentiellement, d'abord, obtenez une liste de toutes les clés uniques qui vous intéressent.

Ensuite, effectuez une recherche en utilisant chacune de ces clés et supprimez si cette recherche renvoie plus d'un.

  db.collection.distinct("key").forEach((num)=>{
    var i = 0;
    db.collection.find({key: num}).forEach((doc)=>{
      if (i)   db.collection.remove({key: num}, { justOne: true })
      i++
    })
  });
-1
répondu Fernando 2017-08-23 12:42:28