Skip to main content

MongoDB删除重复数据

00:00/00:00

最近工作中遇到了这样一个需求,需要根据某个字段删除重复的数据,看了一下官网,好像没有提供直接删除的方法和函数,于是使用搜索引擎检索了一下,发现有一部分推荐使用的是:在集合上创建唯一索引进行删除,具体做法如下:

db.<coll>.createIndex({<field>:1}{{"unique":true,"dropDups":true}})

或者

db.<coll>.ensureIndex({<field>:1}{{"unique":true,"dropDups":true}})

不过查阅了下官方文档从3.0开始就不提供该参数了。此外,该方法也无法选择删除重复数据中的哪一条数据。

也有人推荐使用两个for循环,查找出重复数据,先全部删除,然后再重新插入需要的数据,大致代码如下:

var res=db.<coll>.find();
while(res.hasNext()){
      var res1=db.<coll>.find();
      var re=res.next();
      while(res1.hasNext()){
              var re1=res1.next();
              if(re.<field>==re1.<field>){
                   db.<coll>.remove({"<field>":re1.<field>});
               }
       }
       db.<coll>.insert(re);
}

上述方法还算比较灵活,但是如果数据量很大的话,两个for循环的复杂度过高,所需时间过长,因此强烈不推荐

下面介绍两个推荐的方式:

  • 先遍历一遍,得到最小的_id,删除之后的文档:
db.<coll>.find({},{<field>:1})
.forEach(function(doc){
db.<coll>.remove({_id:{$gt:doc._id},<field>:doc.<field>});
})
  • 使用聚合aggregate
db.<coll>.aggregate([
{
    group: { _id: {: '<field>'},count: {sum: 1},dups: {addToSet: '_id'}}     },     {         match: {count: {gt: 1}}     } ]).forEach(function(doc){     doc.dups.shift();     db..remove({_id: {in: doc.dups}});
})

稍微解释一下这个聚合:首先,根据字段<field>进行分组(可以设置多个),会将字段相同的放在同一个group中,然后使用count进行计数,由于$group只会返回_id中统计的字段,而后面再进行删除的时候回使用到_id这个字段,因此需要使用$addToSet_id加入dups变量中;$match中匹配的是数目大于1的文档集合;对于所有数目大于1的文档集,doc.dups.shift()表示剔除文档集中的第一个_id,防止后面的删除操作将所有文档进行删除。

上面两种方式都比较灵活,可以不限定于删除一个字段相同,也可以根据实际情况增加其它字段。我之前使用的是第一种方式,因为在删除的时候可以比较灵活选择删除哪一个,保留哪一个。不过感觉第二中方式应该会比较高效,特别是在重复的数量不多的情况下,在agrregate阶段会过滤掉很大一部分数据;不过相应地,第二种会消耗更多内存。

打赏
微信扫一扫支付
微信logo微信扫一扫, 打赏作者吧~

mickey

记录生活,写给几十年后的自己。