如何根据Mongodb中的键删除重复项?

时间:2012-11-02 06:24:38

标签: mongodb optimization duplicates key

我在MongoDB中有一个集合,其中有大约(约300万条记录)。我的样本记录看起来像,

 { "_id" = ObjectId("50731xxxxxxxxxxxxxxxxxxxx"),
   "source_references" : [
                           "_id" : ObjectId("5045xxxxxxxxxxxxxx"),
                           "name" : "xxx",
                           "key" : 123
                          ]
 }

我在集合中有很多重复记录,具有相同的source_references.key。 (重复我的意思是,source_references.key不是_id)。

我想删除基于source_references.key的重复记录,我正在考虑编写一些PHP代码来遍历每条记录并删除记录(如果存在)。

有没有办法删除Mongo Internal命令行中的重复项?

9 个答案:

答案 0 :(得分:76)

如果您确定source_references.key标识了重复记录,则可以确保在MongoDB 2.6或更早版本中使用dropDups:true索引创建选项的唯一索引:

db.things.ensureIndex({'source_references.key' : 1}, {unique : true, dropDups : true})

这将保留每个source_references.key值的第一个唯一文档,并删除任何可能导致重复密钥违规的后续文档。

重要提示

明显谨慎:备份数据库,如果您担心意外数据丢失,请先在暂存环境中尝试此操作。

答案 1 :(得分:47)

这是我在MongoDB 3.2上使用的最简单的查询

db.myCollection.find({}, {myCustomKey:1}).sort({_id:1}).forEach(function(doc){
    db.myCollection.remove({_id:{$gt:doc._id}, myCustomKey:doc.myCustomKey});
})

在运行此customKey之前将其编入索引以提高速度

答案 2 :(得分:29)

aggregation framework删除重复项。

一个。如果你想一次删除。

var 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
  }}
])               // 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
  }}
])               // 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 :(得分:8)

虽然@ Stennie是一个有效的答案,但这不是唯一的方法。事实上,MongoDB手册要求您在执行此操作时要非常谨慎。还有两个选择

  1. 让MongoDB为您做using Map Reduce
  2. 效率较低的programatically

答案 4 :(得分:3)

Fernando's answer上扩展时,我发现它花费的时间太长,因此我对其进行了修改。

var x = 0;
db.collection.distinct("field").forEach(fieldValue => {
  var i = 0;
  db.collection.find({ "field": fieldValue }).forEach(doc => {
    if (i) {
      db.collection.remove({ _id: doc._id });
    }
    i++;
    x += 1;
    if (x % 100 === 0) {
      print(x); // Every time we process 100 docs.
    }
  });
});

改进基本上是使用文档ID删除,它应该更快,并且还添加了操作进度,您可以将迭代值更改为所需的数量。

此外,在操作之前对字段进行索引会有所帮助。

答案 5 :(得分:2)

我有类似的要求,但我想保留最新的条目。以下查询可用于我的具有数百万条记录和重复项的集合。

/** Create a array to store all duplicate records ids*/
var duplicates = [];

/** Start Aggregation pipeline*/
db.collection.aggregate([
  {
    $match: { /** Add any filter here. Add index for filter keys*/
      filterKey: {
        $exists: false
      }
    }
  },
  {
    $sort: { /** Sort it in such a way that you want to retain first element*/
      createdAt: -1
    }
  },
  {
    $group: {
      _id: {
        key1: "$key1", key2:"$key2" /** These are the keys which define the duplicate. Here document with same value for key1 and key2 will be considered duplicate*/
      },
      dups: {
        $push: {
          _id: "$_id"
        }
      },
      count: {
        $sum: 1
      }
    }
  },
  {
    $match: {
      count: {
        "$gt": 1
      }
    }
  }
],
{
  allowDiskUse: true
}).forEach(function(doc){
  doc.dups.shift();
  doc.dups.forEach(function(dupId){
    duplicates.push(dupId._id);
  })
})

/** Delete the duplicates*/
var i,j,temparray,chunk = 100000;
for (i=0,j=duplicates.length; i<j; i+=chunk) {
    temparray = duplicates.slice(i,i+chunk);
    db.collection.bulkWrite([{deleteMany:{"filter":{"_id":{"$in":temparray}}}}])
}

答案 6 :(得分:1)

这是一种稍微“手动”的方式:

基本上,首先,获取您感兴趣的所有唯一键的列表。

然后使用其中每个键执行搜索,如果搜索返回大于1,则删除。

var Electron = require('electron')

答案 7 :(得分:0)

pip install mongo_remove_duplicate_indexes

  1. 使用任何语言创建脚本
  2. 遍历您的收藏
  3. 在此集合中创建新集合并创建新索引,并将唯一设置设置为true,请记住此索引必须与索引相同您希望从具有相同名称的原始集合中删除重复项 对于前 - 你有一个集合游戏,并在这个集合中你有字段类型,包含重复,你希望删除,所以只需创建新的集合 db.createCollection(&#34; CNAME&#34) 创建新索引 db.cname.createIndex({&#39;类型&#39;:1}的,独特的:1) 现在,当您将插入具有相似类型的文档时,将首先接受,其他将被拒绝与duplicae密钥错误
  4. 现在只需将收到的json格式值插入到新集合中,并使用异常处理处理异常 对于ex pymongo.errors.DuplicateKeyError
  5. 查看mongo_remove_duplicate_indexes的软件包源代码,以便更好地理解

答案 8 :(得分:0)

如果你有足够的内存,你可以在scala中做类似的事情:

cole.find().groupBy(_.customField).filter(_._2.size>1).map(_._2.tail).flatten.map(_.id)
.foreach(x=>cole.remove({id $eq x})