2013-01-06 8 views
27

Mam około 1,7 mln dokumentów w mongodb (w przyszłości 10m +). Niektóre z nich reprezentują zduplikowane wpisy, których nie chcę. Struktura dokumentu jest coś takiego:Najszybszy sposób na usunięcie duplikatów dokumentów w mongodb

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

Dokument powielać jeżeli ma co najmniej jednego węzła samym jako kolejny dokument z samej nazwie. Jaki jest najszybszy sposób na usunięcie duplikatów?

Odpowiedz

37

Zakładając chcesz trwale usunąć dokumenty, które zawierają duplikat name + nodes wpis z kolekcji, można dodać indeks unique z opcją dropDups: true:

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

Jak docs powiedzieć, należy zachować szczególną ostrożność przy tak, ponieważ spowoduje to usunięcie danych z bazy danych. Najpierw wykonaj kopię zapasową bazy danych, jeśli nie działa dokładnie tak, jak się spodziewasz.

UPDATE

To rozwiązanie jest ważna tylko przez MongoDB 2.x jako opcja dropDups nie jest już dostępna w wersji 3.0 (docs) jest.

+0

Nazwa nie musi być niepowtarzalna. Czy usunie to tylko wtedy, gdy zarówno nazwa, jak i co najmniej jeden węzeł będą takie same? – ewooycom

+4

@ user1188570 Jest to związek, więc oba pola muszą mieć duplikat w tym samym dokumencie. – Sammaye

+0

@Samaye Myślę, że jego lepszym rozwiązaniem do scalania węzłów, czy jest coś takiego jak akcja: {$ scal: węzły} zamiast dropDups? Jak byś to osiągnął? – ewooycom

44

dropDups: true Opcja nie jest dostępna w wersji 3.0.

Mam rozwiązanie ze strukturą agregacji do zbierania duplikatów i usuwania za jednym razem.

Może być nieco wolniejsza niż zmiany "indeksu" na poziomie systemu. Ale to dobrze, biorąc pod uwagę sposób, w jaki chcesz usunąć duplikaty dokumentów.

a. Usuń wszystkie dokumenty za jednym razem: 1. Możesz usuwać dokumenty jeden po drugim.

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

Jeśli wynik nie jest używany, oznacza to, że mam problem z TypeError – sara

+1

Dziękujemy za pomocną odpowiedź! Znalazłem to lepiej, gdy masz dużo wierszy (mam 5M wierszy), że lepiej jest utworzyć licznik i ograniczyć go dla każdego 10K, a nie dla całego duplikatu, bo może być po prostu za duży :) – Mazki516

+0

To wygląda świetnie! Czy masz jakieś zalecenia dotyczące wydajności? Mam około 3M wierszy z kilkoma dups. Czy lepiej zrobić to za jednym razem (rozwiązanie a), czy jedno po drugim? – Nico

12

Tworzenie kolekcji zrzut z mongodump

jasny zbieranie

Dodaj unikatowy indeks

Restore kolekcję mongorestore

+0

To był zdecydowanie najłatwiejszy sposób na zrobienie tego - kilka minut przestojów, aby uratować stres związany z koniecznością uruchomienia nieznanego zapytania. – misaka

+0

Jest to łatwiejszy i bardziej intuicyjny sposób. Dzięki. – Nerzid

+0

Dzięki, czy mogę wyjaśnić, że odtworzenie kolekcji po dodaniu unikalnego indeksu oznacza, że ​​nie wystąpi błąd podczas próby duplikowania wpisu? – memebrain

5

Znalazłem rozwiązanie, które działa z MongoDB 3.4: I Założę się, że pole z duplikatami nazywa się 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}) 

Będąc nowicjuszem w mongoDB, spędziłem dużo czasu i wykorzystałem inne długotrwałe rozwiązania, aby znaleźć i usunąć duplikaty. Myślę jednak, że to rozwiązanie jest zgrabne i łatwe do zrozumienia.

Działa przez pierwsze pasujące dokumenty, które zawierają pole X (miałem kilka dokumentów bez tego pola, a otrzymałem jeden dodatkowy pusty wynik).

Następny etap grupuje dokumenty według pólX i wstawia tylko dokument $first w każdej grupie przy użyciu $$ROOT.Na końcu zastępuje całą zagregowaną grupę dokumentem znalezionym za pomocą $ first i $$ ROOT.

Musiałem dodać allowDiskUse, ponieważ moja kolekcja jest duża.

Możesz dodać to po dowolnej liczbie potoków i chociaż dokumentacja dla $ pierwszy wspomina o etapie sortowania przed użyciem $ najpierw, zadziałało to dla mnie bez niego. „Mogliśmy opublikować link tutaj, moja reputacja jest mniej niż 10 :(”

można zapisać wyniki do nowej kolekcji dodając $ na scenie ...

Alternatywnie, jeśli jest tylko zainteresowany w kilku dziedzinach, np pole1, pole2, a nie cały dokument, w fazie grupowej bez 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}) 
0

Oto nieco bardziej „ręczny” sposób to zrobić:

Zasadniczo pierwszy , dostać listę wszystkich unikalnych kluczy, które Cię interesują.

Następnie wykonaj wyszukiwanie przy użyciu każdego z tych klawiszy i usuń, jeśli wyszukiwanie zwraca więcej niż jeden.

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++ 
    }) 
    }); 
0
  1. ogólne pomysłem jest użycie findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ aby pobrać jeden losowy identyfikator ze zduplikowanych rekordów w zbiorze.

  2. Usuń wszystkie rekordy z innego zbioru niż identyfikator losowy, który pobraliśmy z opcji findOne.

Możesz zrobić coś takiego, jeśli próbujesz to zrobić w 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"}}]) 

z powłoki:

  1. Wymień find_one do findOne
  2. samo polecenie Remove powinno działać.
Powiązane problemy