2016-01-11 40 views
6

我有几个集合(整体〜15密耳文档)和文档的数据库看起来像这样(简化):pymongo:删除重复(图减少?)

{'Text': 'blabla', 'ID': 101} 
{'Text': 'Whuppppyyy', 'ID': 102} 
{'Text': 'Abrakadabraaa', 'ID': 103} 
{'Text': 'olalalaal', 'ID': 104} 
{'Text': 'test1234545', 'ID': 104} 
{'Text': 'whapwhapwhap', 'ID': 104} 

他们都有一个唯一的_id字段作为好吧,但我想删除符合其他字段(外部ID字段)的副本。

首先,我尝试了一个非常手动的方法与列表并删除之后,但数据库似乎太大,需要很长时间,并不实际。其次,以下在当前的MongoDB版本中不起作用,即使有人提出这个建议。

db.collection.ensureIndex({ ID: 1 }, { unique: true, dropDups: true }) 

所以,现在我想创建一个映射精简解决方案,但我真的不知道我在做什么,尤其是在使用另一个字段(不是数据库_id)查找并删除重复的难度。这是我的坏第一种方法(从一些INTERENT源通过):

map = Code("function(){ if(this.fieldName){emit(this.fieldName,1);}}") 
reduce = Code("function(key,values) {return Array.sum(values);}") 
res = coll.map_reduce(map,reduce,"my_results"); 

response = [] 
for doc in res.find(): 
    if(doc['value'] > 1): 
     count = int(doc['value']) - 1 
     docs = col.find({"fieldName":doc['ID']},{'ID':1}).limit(count) 
     for i in docs: 
      response.append(i['ID']) 

coll.remove({"ID": {"$in": response}}) 

任何帮助,以减少外部ID字段的任何副本(留一个条目),将是非常apprechiated;)谢谢!

回答

4

另一种方法是使用aggregation framework,它比map-reduce具有更好的性能。考虑下面聚集的管道,其作为聚合管道的第一阶段,由ID$group操作员组的文件和存储在unique_ids领域的使用$addToSet运营商分组的记录每个_id值。累加器运算符累加传递给它的字段的值,在这种情况下为常数1 - 从而将分组记录的数量计入到计数字段中。另一个流水线步骤$match过滤计数至少为2的文档,即重复。

一旦你从聚集的结果,你迭代光标来移除第一_idunique_ids字段,然后推休息到稍后将用于去除重复(减去一个条目)数组:

cursor = db.coll.aggregate(
    [ 
     {"$group": {"_id": "$ID", "unique_ids": {"$addToSet": "$_id"}, "count": {"$sum": 1}}}, 
     {"$match": {"count": { "$gte": 2 }}} 
    ] 
) 

response = [] 
for doc in cursor: 
    del doc["unique_ids"][0] 
    for id in doc["unique_ids"]: 
     response.append(id) 

coll.remove({"_id": {"$in": response}}) 
+0

的MongoDB 2.6是告诉我DeprecationWarning:除去已被弃用。改为使用delete_one或delete_many。 – wordsforthewise

3

首先,我试着用列表和删除事后很手动方法,但似乎DB过大,需要很长而且是不实际的。

最好的办法是使用.aggregate()方法,它提供对聚合管道的访问来查找那些重复的文档。在管道中的第一阶段是$group阶段,你组文档由复制键然后使用$push$sum蓄电池运营商分别在组中返回所有_id每个组和计数元素的数组。流水线中的下一个和最后一个阶段是$match阶段,只有那些有重复“ID”的结果才会返回。然后从那里迭代光标并使用"bulk"操作更新每个文档。

pipeline = [{'$group': {'_id': '$ID', 'count': {'$sum': 1}, 'ids': {'$push': '$_id'}}}, 
    {'$match': {'count': {'$gte': 2}}}] 

bulk = db.collection.initialize_ordered_bulk_op() 
count = 0 
for document in db.collection.aggregate(pipeline): 
    it = iter(document['ids']) 
    next(it) 
    for id in it: 
     bulk.find({'_id': id}).remove_one({'_id': id}) 
     count = count + 1 
     if count % 1000 == 0: 
      bulk.execute() 
    if count > 0: 
     bulk.execute() 

MongoDB的3.2不赞成Bulk()及其相关的方法,所以你需要使用bulk_write()方法来执行你的要求。

from pymongo import DeleteOne 

request = [] 
for document in db.collection.aggregate(pipeline): 
    it = iter(document['ids']) 
    next(it) 
    for id in it: 
     requests.append(DeleteOne({'_id': id})) 
db.collection.bulk_write(requests) 

你也可以做到这一点在shell如图所示接受答案remove dups from mongodbHow to remove duplicates with a certain condition in mongodb?