Удалить дубликаты в 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 }
Эта:
$group
s документами по полю, определяющему, что такое дубликат (здесьx
) и накапливает сгруппированные документы, сохраняя только один ($first
найдено) и присвоив ему значение$$ROOT
, который является самим документом. В конце этого этапа у нас есть что-то вроде:{ "_id" : "a", "onlyOne" : { "x" : "a", "y" : 27 } } { "_id" : "b", "onlyOne" : { "x" : "b", "y" : 12 } }
$replaceWith
все существующие поля во входном документе с содержаниемonlyOne
поле, которое мы создали в$group
stage, чтобы вернуть исходный формат. В конце этого этапа у нас есть что-то вроде:{ "x" : "a", "y" : 27 } { "x" : "b", "y" : 12 }
$replaceWith
доступно только начиная сMongo 4.2
. С предыдущими версиями мы можем использовать$replaceRoot
вместо:{ $replaceRoot: { newRoot: "$onlyOne" } }
$out
вставляет результат конвейера агрегации в ту же коллекцию. Обратите внимание, что$out
удобно заменяет содержимое указанной коллекции, делая это решение возможным.
Я использовал такой подход:
- Возьмем дамп монго конкретной коллекции.
- Очистить эту коллекцию
- Добавить уникальный ключевой индекс
- Восстановить дамп с помощью 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"}}}
])
и сделать цикл через массив идентификаторов имеет смысл для вас?