it-swarm.dev

Schnellster Weg, um doppelte Dokumente in Mongodb zu entfernen

Ich habe ungefähr 1.7M Dokumente in Mongodb (zukünftig 10m +). Einige stellen einen doppelten Eintrag dar, den ich nicht möchte. Die Struktur des Dokuments sieht etwa so aus:

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

Das Dokument ist doppelt vorhanden, wenn es unter mindestens ein Knoten ist gleich wie ein anderes Dokument mit gleichem Namen vorhanden ist. Was ist der schnellste Weg, um Duplikate zu entfernen?

30
ewooycom

Angenommen, Sie möchten Dokumente, die einen doppelten name + nodes-Eintrag enthalten, dauerhaft aus der Sammlung löschen, können Sie mit der Option dropDups: true einen unique-Index hinzufügen:

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

Wie die Dokumente sagen, seien Sie äußerst vorsichtig, da dadurch Daten aus Ihrer Datenbank gelöscht werden. Sichern Sie Ihre Datenbank zuerst, falls sie nicht genau wie erwartet funktioniert.

UPDATE

Diese Lösung ist nur über MongoDB 2.x gültig, da die Option dropDups in 3.0 nicht mehr verfügbar ist ( docs ).

41
JohnnyHK

Die dropDups: true-Option ist in 3.0 nicht verfügbar. 

Ich habe eine Lösung mit Aggregationsrahmen, um Duplikate zu sammeln und dann auf einmal zu entfernen.

Es ist möglicherweise etwas langsamer als Änderungen auf Indexebene. Es ist jedoch gut, wenn Sie überlegen, wie Sie doppelte Dokumente entfernen möchten. 

ein. Entfernen Sie alle Dokumente auf einmal

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. Sie können Dokumente einzeln löschen.

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
})
54
Somnath Muluk

Erstellen Sie einen Sammlungsdump mit Mongodump

Sammlung löschen

Fügen Sie einen eindeutigen Index hinzu

Stellen Sie die Kollektion mit Mongorestore wieder her

19
dhythhsba

Ich habe diese Lösung gefunden, die mit MongoDB 3.4 funktioniert: Ich gehe davon aus, dass das Feld mit Duplikaten FeldX heißt

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

Da ich neu bei MongoDB bin, habe ich viel Zeit investiert und andere langwierige Lösungen zum Suchen und Löschen von Duplikaten verwendet. Ich denke jedoch, dass diese Lösung ordentlich und leicht verständlich ist.

Es funktioniert, indem zuerst Dokumente mit FeldX abgeglichen werden (Ich hatte einige Dokumente ohne dieses Feld und erhielt ein zusätzliches leeres Ergebnis). 

Die nächste Stufe gruppiert Dokumente nach fieldX und fügt nur das Dokument $ first in jede Gruppe mit $$ ROOT ein. Schließlich wird die gesamte aggregierte Gruppe durch das mit $ first und $$ ROOT gefundene Dokument ersetzt.

Ich musste allowDiskUse hinzufügen, da meine Sammlung groß ist.

Sie können dies nach einer beliebigen Anzahl von Pipelines hinzufügen, und obwohl in der Dokumentation für $ first eine Sortierstufe vor der Verwendung von $ first erwähnt wird, funktionierte es für mich ohne. "Konnte hier keinen Link posten, mein Ruf ist weniger als 10 :("

Sie können die Ergebnisse in einer neuen Sammlung speichern, indem Sie eine $ out-Stufe hinzufügen ...

Alternativ, wenn nur einige Felder interessiert sind, z. Feld1, Feld2 und nicht das gesamte Dokument in der Gruppenphase ohne 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})
7
Ali Abul Hawa
  1. Eine allgemeine Idee ist, findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ Zu verwenden, um eine zufällige ID aus den doppelten Datensätzen in der Sammlung abzurufen.

  2. Löschen Sie alle Datensätze in der Sammlung mit Ausnahme der Zufalls-ID, die wir mit der Option findOne abgerufen haben.

Sie können so etwas tun, wenn Sie versuchen, es in Pymongo zu tun.

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"}}])

Aus der Schale:

  1. Ersetzen Sie find_one durch findOne
  2. Der gleiche Befehl zum Entfernen sollte funktionieren.
0
amateur

Mit pymongo sollte dies funktionieren.

Fügen Sie in unique_field die Felder hinzu, die für die Auflistung eindeutig sein müssen

unique_field = {"field1":"$field1","field2":"$field2"}

cursor = DB.COL.aggregate([{"$group":{"_id":unique_field, "dups":{"$Push":"$uuid"}, "count": {"$sum": 1}}},{"$match":{"count": {"$gt": 1}}},{"$group":"_id":None,"dups":{"$addToSet":{"$arrayElemAt":["$dups",1]}}}}],allowDiskUse=True)

schneide das Dups-Array abhängig von der Anzahl der Duplikationen auf (hier hatte ich nur ein zusätzliches Duplikat für alle)

items = list(cursor)
removeIds = items[0]['dups']
hold.remove({"uuid":{"$in":removeIds}})
0
Renny

Die folgende Methode führt Dokumente mit demselben Namen zusammen, wobei nur die eindeutigen Knoten erhalten bleiben, ohne sie zu duplizieren.

Ich fand die Verwendung des $out-Operators einfach. Ich wickle das Array ab und gruppiere es, indem ich es setze. Mit dem Operator $out kann das Aggregationsergebnis [docs] beibehalten. Wenn Sie den Namen der Sammlung selbst eingeben, wird die Sammlung durch die neuen Daten ersetzt. Wenn der Name nicht vorhanden ist, wird eine neue Sammlung erstellt.

Hoffe das hilft.

allowDiskUse muss möglicherweise zur Pipeline hinzugefügt werden.

db.collectionName.aggregate([
  {
    $unwind:{path:"$nodes"},
  },
  {
    $group:{
      _id:"$name",
      nodes:{
        $addToSet:"$nodes"
      }
  },
  {
    $project:{
      _id:0,
      name:"$_id.name",
      nodes:1
    }
  },
  {
    $out:"collectionNameWithoutDuplicates"
  }
])
0
sanair96