【问题标题】:Remove duplicate in MongoDB删除 MongoDB 中的重复项
【发布时间】:2016-02-29 19:04:40
【问题描述】:

我有一个名为“contact_id”字段的集合。 在我的收藏中,我有使用此键的重复寄存器。

我怎样才能删除重复的,只剩下一个寄存器?

我已经试过了:

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

但是没用,因为函数dropDups在MongoDB 3.x中不再可用

我正在使用 3.2

【问题讨论】:

    标签: mongodb mongodb-query duplicates


    【解决方案1】:

    是的,dropDups 已经一去不复返了。但是你绝对可以通过一点点努力实现你的目标。

    你需要先找到所有重复的行,然后删除除first之外的所有行。

    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,然后删除 dups 数组中剩余 _id 的所有文档。

    上面的脚本将删除所有重复的文档。

    【讨论】:

    • 嗨。部分工作。当我放入一个小集合时效果很好。但是当我在一个大集合中执行时,数据库“锁定”并且其他查询超时。
    • 嗯,这是意料之中的,尤其是在非常大的收藏中。 MongoDB 聚合管道限制为 100MB RAM 使用量。见docs.mongodb.org/manual/aggregation。再次,您的问题没有提及您的收藏大小。此解决方案让您了解如何解决问题,但并未涵盖所有边缘情况。所以你也需要付出一些努力。
    • 感谢您的帮助。我会搜索一下。
    • 对于大型收藏,您可能需要使用Bulk Write Operations 功能。
    【解决方案2】:

    这对于 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}});
    

    【讨论】:

      【解决方案3】:

      我们还可以使用$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 输入文档中的所有现有字段与我们在$group 阶段创建的onlyOne 字段的内容,以便找到原始格式。在这个阶段结束时,我们有类似的东西:

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

        $replaceWith 仅从Mongo 4.2 开始可用。对于之前的版本,我们可以使用$replaceRoot

        { $replaceRoot: { newRoot: "$onlyOne" } }
        
      • $out 将聚合管道的结果插入到同一个集合中。请注意,$out 方便地替换了指定集合的​​内容,使此解决方案成为可能。

      【讨论】:

        【解决方案4】:

        也许最好尝试创建一个 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"}}}
        ])
        

        并通过 ids 数组进行循环 这对你有意义吗?

        【讨论】:

          【解决方案5】:

          我用过这种方法:

          1. 获取特定集合的 mongo 转储。
          2. 清除该集合
          3. 添加唯一键索引
          4. 使用 mongorestore 恢复转储。

          【讨论】:

            猜你喜欢
            • 2015-10-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-04-23
            • 2016-03-28
            • 2012-10-22
            • 1970-01-01
            相关资源
            最近更新 更多