Подтвердить что ты не робот

Самый быстрый способ удаления дубликатов документов в mongodb

У меня есть около 1.7M документов в mongodb (в будущем 10m +). Некоторые из них представляют собой дублирующую запись, которую я не хочу. Структура документа выглядит примерно так:

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

Документ дублируется, если он имеет минимум node тот же, что и другой документ с тем же именем. Каков самый быстрый способ удаления дубликатов?

4b9b3361

Ответ 1

Предполагая, что вы хотите навсегда удалить документы, содержащие дублируемую запись name + nodes из коллекции, вы можете добавить индекс unique с помощью dropDups: true:

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

Как говорится в документах, используйте с ним особую осторожность, поскольку он удалит данные из вашей базы данных. Сначала создайте резервную копию своей базы данных, если она не делает то, что вы ожидаете.

UPDATE

Это решение действует только через MongoDB 2.x, поскольку опция dropDups больше недоступна в версии 3.0 (docs).

Ответ 2

dropDups: true опция недоступна в версии 3.0.

У меня есть решение с основанием агрегации для сбора дубликатов, а затем удаления за один раз.

Это может быть несколько медленнее, чем изменения индекса индекса системы. Но это хорошо, рассматривая способ удаления дубликатов документов.

а. Удалите все документы за один раз

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

б. Вы можете удалять документы по одному.

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

Ответ 3

Создание дампа коллекции с помощью mongodump

Очистить коллекцию

Добавить уникальный индекс

Восстановить коллекцию с помощью mongorestore

Ответ 4

Я нашел это решение, которое работает с MongoDB 3.4: я предполагаю, что поле с дубликатами называется 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})

Будучи новичком в mongoDB, я потратил много времени и использовал другие длинные решения для поиска и удаления дубликатов. Тем не менее, я думаю, что это решение аккуратно и легко понять.

Он работает, сначала сопоставляя документы, которые содержат fieldX (у меня было несколько документов без этого поля, и я получил один дополнительный пустой результат).

Следующий этап группирует документы по fieldX и вставляет только $ first документ в каждую группу, используя $$ ROOT. Наконец, он заменяет всю агрегированную группу документом, найденным с использованием $ first и $$ ROOT.

Мне пришлось добавить allowDiskUse, потому что моя коллекция большая.

Вы можете добавить это после любого числа конвейеров, и хотя в документации для $ first упоминается этап сортировки до использования $ first, у меня это работало без него. "не могу опубликовать ссылку здесь, моя репутация меньше 10 :("

Вы можете сохранить результаты в новой коллекции, добавив этап $ out...

Альтернативно, если кто-то интересуется только несколькими полями, например, field1, field2, а не целым документом, на групповом этапе без 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})

Ответ 5

  • Общая идея заключается в использовании findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ для извлечения одного случайного идентификатора из дубликатов записей в коллекции.

  • Удалите все записи в коллекции, отличные от случайного идентификатора, который мы извлекли из опции findOne.

Вы можете сделать что-то подобное, если вы пытаетесь сделать это в 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"}}])

Из оболочки:

  • Заменить find_one для findOne
  • Такая же команда удаления должна работать.

Ответ 6

Следующий метод объединяет документы с одинаковыми именами, сохраняя только уникальные узлы, не дублируя их.

Я обнаружил, что использование оператора $out является простым способом. Я раскручиваю массив, а затем группирую его, добавляя в набор. Оператор $out позволяет сохранить результат агрегирования [docs]. Если вы введете имя самой коллекции, она заменит коллекцию новыми данными. Если имя не существует, оно создаст новую коллекцию.

Надеюсь это поможет.

allowDiskUse возможно, придется добавить в конвейер.

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

Ответ 7

При использовании pymongo это должно сработать.

Добавьте поля, которые должны быть уникальными для коллекции, в unique_field

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)

нарезать массив dups в зависимости от количества дубликатов (здесь у меня был только один дополнительный дубликат для всех)

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

Ответ 8

Вот несколько более "ручной" способ сделать это:

По сути, сначала получите список всех уникальных ключей, которые вам интересны.

Затем выполните поиск с использованием каждого из этих ключей и удалите, если этот поиск вернется больше одного.

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