2016-01-11 27 views
6

ho un database con diverse collezioni (complessivi ~ documenti 15mil) e documenti simile a questa (semplificato):pymongo: rimuovere i duplicati (mappa a ridurre?)

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

Tutti hanno un campo _id unico come bene, ma voglio eliminare i duplicati in un altro campo (il campo ID esterno).

In primo luogo, ho provato un approccio molto manuale con elenchi e la cancellazione in seguito, ma il DB sembra troppo grande, richiede molto tempo e non è pratico.

In secondo luogo, quanto segue non funziona più nelle attuali versioni di MongoDB, anche se qualcuno lo suggerisce.

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

Così, ora sto cercando di creare una mappa a ridurre soluzione, ma io non so davvero cosa fare Im e soprattutto hanno difficoltà ad usare un altro campo (non il _id database) per trovare ed eliminare i duplicati. Ecco il mio cattivo approccio prima (adottato da qualche fonte 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}}) 

Qualsiasi contribuire a ridurre i duplicati nel campo ID esterno (lasciando una voce), sarebbe molto apprechiated;) Grazie!

risposta

4

Un approccio alternativo consiste nell'utilizzare lo aggregation framework che offre prestazioni migliori rispetto alla riduzione della mappa. Si consideri il seguente gasdotto di aggregazione che, come la prima fase della pipeline di aggregazione, le $group gruppi di operatori documenti da parte del settore ID e negozi nel campo unique_ids ogni valore _id dei record raggruppati usando l'operatore $addToSet. L'operatore accumulatore $sum somma i valori dei campi passati ad esso, in questo caso la costante 1, contando quindi il numero di record raggruppati nel campo di conteggio. L'altro passaggio della pipeline $match filtra i documenti con un conteggio minimo di 2, ovvero i duplicati.

Una volta a ottenere il risultato dall'aggregazione, eseguire iterazioni il cursore per rimuovere il primo _id nel campo unique_ids, quindi spingere il resto in un array che sarà utilizzato in seguito per rimuovere i duplicati (meno una voce):

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 sta dicendo mi DeprecationWarning: rimuovere è deprecato. Utilizza invece delete_one o delete_many. – wordsforthewise

3

In primo luogo, ho provato un approccio molto manuale con liste e l'eliminazione dopo, ma il DB sembra troppo grande, richiede molto tempo e non è pratico.

La soluzione migliore è utilizzare il metodo .aggregate() che fornisce l'accesso alla pipeline di aggregazione per trovare quei documenti che sono duplicati. La prima fase della pipeline è il $group fase in cui si raggruppare i documenti da parte duplicati chiave quindi utilizzare le $push e $sum accumulatori operatori che restituiscono, rispettivamente, una matrice di tutti _id per ciascun gruppo e la conteggio di elementi nel gruppo . La fase successiva e ultima della pipeline è la fase $match per restituire solo quei risultati in cui è presente un "ID" duplicato. Da lì, quindi, iterare il cursore e aggiornare ogni documento utilizzando le operazioni "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 depreca Bulk() e dei suoi metodi associati per cui sarà necessario utilizzare il metodo bulk_write() per eseguire la vostra richiesta.

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) 

si può anche fare questo nella shell, come mostrato nelle risposte accettato di remove dups from mongodb e How to remove duplicates with a certain condition in mongodb?