Удалить дубликаты в MongoDB

У меня есть коллекция с полем с именем "contact_id". В моей коллекции есть дубликаты регистров с этим ключом.

Как я могу удалить дубликаты, в результате чего только один регистр?

Я уже попробовал:

db.PersonDuplicate.ensureIndex({"contact_id": 1}, {unique: true, dropDups: true}) 

Но не сработало, потому что функция dropDups больше не доступна в MongoDB 3.x

Я использую 3.2

Спасибо

3 ответа

Решение

Да, dropDups ушел навсегда. Но вы определенно можете достичь своей цели с небольшим усилием.

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

db.dups.aggregate([{$group:{_id:"$contact_id", dups:{$push:"$_id"}, count: {$sum: 1}}},
{$match:{count: {$gt: 1}}}
]).forEach(function(doc){
  doc.dups.shift();
  db.dups.remove({_id : {$in: doc.dups}});
});

Как вы видите doc.dups.shift() сначала удалит _id из массива, а затем удалит все документы с оставшимися _ids в массиве dups.

Сценарий выше удалит все дубликаты документов.

Это хороший шаблон для mongod 3+, который также гарантирует, что вы не будете использовать нашу память, что может случиться с действительно большими коллекциями. Вы можете сохранить его в файле dedup.js, настроить его и запустить для своей базы данных с помощью: mongo localhost:27017/YOURDB dedup.js

var duplicates = [];

db.runCommand(
  {aggregate: "YOURCOLLECTION",
    pipeline: [
      { $group: { _id: { DUPEFIELD: "$DUPEFIELD"}, dups: { "$addToSet": "$_id" }, count: { "$sum": 1 } }},
      { $match: { count: { "$gt": 1 }}}
    ],
    allowDiskUse: true }
)
.result
.forEach(function(doc) {
    doc.dups.shift();
    doc.dups.forEach(function(dupId){ duplicates.push(dupId); })
})
printjson(duplicates); //optional print the list of duplicates to be removed

db.YOURCOLLECTION.remove({_id:{$in:duplicates}});

Мы также можем использовать $out Этап для удаления дубликатов из коллекции путем замены содержимого коллекции только одним экземпляром на дубликат.

Например, чтобы сохранить только один элемент на значение x:

// > db.collection.find()
//     { "x" : "a", "y" : 27 }
//     { "x" : "a", "y" : 4  }
//     { "x" : "b", "y" : 12 }
db.collection.aggregate(
  { $group: { _id: "$x", onlyOne: { $first: "$$ROOT" } } },
  { $replaceWith: "$onlyOne" }, // prior to 4.2: { $replaceRoot: { newRoot: "$onlyOne" } }
  { $out: "collection" }
)
// > db.collection.find()
//     { "x" : "a", "y" : 27 }
//     { "x" : "b", "y" : 12 }

Эта:

  • $groups документами по полю, определяющему, что такое дубликат (здесь x) и накапливает сгруппированные документы, сохраняя только один ($first найдено) и присвоив ему значение $$ROOT, который является самим документом. В конце этого этапа у нас есть что-то вроде:

    { "_id" : "a", "onlyOne" : { "x" : "a", "y" : 27 } }
    { "_id" : "b", "onlyOne" : { "x" : "b", "y" : 12 } }
    
  • $replaceWith все существующие поля во входном документе с содержанием onlyOne поле, которое мы создали в $groupstage, чтобы вернуть исходный формат. В конце этого этапа у нас есть что-то вроде:

    { "x" : "a", "y" : 27 }
    { "x" : "b", "y" : 12 }
    

    $replaceWith доступно только начиная с Mongo 4.2. С предыдущими версиями мы можем использовать $replaceRoot вместо:

    { $replaceRoot: { newRoot: "$onlyOne" } }
    
  • $out вставляет результат конвейера агрегации в ту же коллекцию. Обратите внимание, что$out удобно заменяет содержимое указанной коллекции, делая это решение возможным.

Я использовал такой подход:

  1. Возьмем дамп монго конкретной коллекции.
  2. Очистить эту коллекцию
  3. Добавить уникальный ключевой индекс
  4. Восстановить дамп с помощью mongorestore.

Может быть, стоит попытаться создать tmpColection, создать уникальный индекс, затем скопировать данные из источника, и последним шагом будет замена имен?

У меня была другая идея - получить удвоенные индексы в массиве (используя агрегацию), а затем выполнить цикл через вызов метода remove() с параметром justOne, установленным в true или 1.

 var itemsToDelete = db.PersonDuplicate.aggregate([
{$group: { _id:"$_id", count:{$sum:1}}},
{$match: {count: {$gt:1}}},
{$group: { _id:1, ids:{$addToSet:"$_id"}}}
])

и сделать цикл через массив идентификаторов имеет смысл для вас?

Другие вопросы по тегам