Как удалить дубликат записи в MongoDB с помощью MapReduce? - PullRequest
5 голосов
/ 06 декабря 2011

У меня очень большая коллекция на MongoDB, и я хочу удалить дубликат записи из этой коллекции.Первая мысль, которая приходит мне в голову - это удалить индекс и восстановить его с помощью dropDups.Тем не менее, дублированные данные слишком много для обработки MongoDB.

Поэтому я обращаюсь за помощью к MapReduce.Вот мой текущий прогресс.

m = function () { 
    emit(this.myid, 1); 
}

r = function (k, vals) { 
    return Array.sum(vals); 
} 

res = db.userList.mapReduce(m,r, { out : "myoutput" });

И все "myid" дубликата записи хранятся в коллекции "myoutput".Однако я не знаю, как удалить запись из userList, ссылаясь на myoutput.myid.Предполагается, что это что-то вроде этого:

db.myoutput.find({value: {$gt: 1}}).forEach(
    function(obj) {
        db.userList.remove(xxxxxxxxx) // I don't know how to do so
})

Кстати, использование foreach, похоже, сотрет все записи со здравым смыслом myid.Но я просто хочу удалить дубликаты записей.Пример:

{ "_id" : ObjectId("4edc6773e206a55d1c0000d8"), "myid" : 0 }
{ "_id" : ObjectId("4edc6780e206a55e6100011a"), "myid" : 0 }

{ "_id" : ObjectId("4edc6784e206a55ed30000c1"), "myid" : 0 }

Окончательный результат должен сохранить только одну запись.Может ли кто-нибудь помочь мне в этом?

Спасибо.:)

Ответы [ 4 ]

8 голосов
/ 07 декабря 2011

Самым чистым, вероятно, является написание клиентского скрипта, который удаляет записи:

db.myoutput.find({value: {$gt: 1}}).forEach(
    function(obj) {
    var cur = db.userList.find({ myid: obj._id }, {_id: 1});
    var first = true;
    while (cur.hasNext()) {
        var doc = cur.next();
        if (first) {first = false; continue;}
        db.userList.remove({ _id: doc._id });
    }
})

Я не проверял этот код, поэтому всегда проверяйте, работает ли он с данными prod.

1 голос
/ 09 апреля 2015

на самом деле здесь нет необходимости в mapreduce.как насчет этого : ?вставьте код в оболочку mongo:

   function removeDupls (collectionName, keyField, reportEvery) {
    if (reportEvery === undefined) {reportEvery=10;}  
    sort = {};
    sort[keyField] = 1;
    var myidLast; 
    var res = {docsCnt:0,docsRemoved:0}
    db[collectionName].find().sort(sort).clone().forEach(
        function(doc) {
                res['docsCnt'] += 1; 
                if (doc.myid == myidLast) {db[collectionName].remove({_id:doc._id}); res['docsRemoved'] +=1;}
                else {myidLast = doc.myid;}
                if (res['docsCnt'] % reportEvery === 0) {print (JSON.stringify(res))} 
            } 
    );
    return res;
}

, затем назовите его:

removeDupls('users','myid',1000)

это будет работать и, вероятно, будет быстрее, чем любой mapreduce> удалить работу (в зависимости от количества дублированныхдокументы) Если вы хотите сделать это очень быстро, вы должны сохранить _ids документов, которые будут удалены во временном массиве, а затем использовать пакетное удаление.

1 голос
/ 12 апреля 2013

Хотя приведенный выше ответ весьма эффективен, он действительно очень медленный, если в вашей базе данных / коллекции содержится 900К или 3М записей.

Если вы имеете дело с большими объемами данных, я предлагаю взять длинный путь:

  • Выберите элементы, используя аналог GROUP BY - db.collection.group ()
  • Сохраните эти данные, используя функцию Reduce в массиве
  • Сохранить экспортированные данные в формате JSON
  • Импортируйте его снова, используя mongoimport в чистую базу данных.

Для 900 тыс. Записей это заняло около 35 с (групповой запрос).

Реализация в PHP:

$mongo_client = new MongoClient();
$collection = $mongo_client->selectCollection("main", "settings");

//Group by the field "code"
$keys = array("code" => 1);
//You must create objects for every field you wish to transfer (except the one grouped by - that gets auto-transferred)
$initial = array("location" => "", "name" => "", "score" => 0, "type" => "");
//The reduce function will set the grouped properties
$reduce = "function (obj, prev) { prev.location = obj.location; prev.name = obj.name;  prev.score = obj.score; prev.type = obj.type; }";

$fh = fopen("Export.json", "w");
$unique_set = $collection->group($keys, $initial, $reduce);
fwrite($fh, json_encode($unique_set['retval']));
fclose($fh);

Если у вас очень мало дубликатов, запуск его на PHP может быть не лучшим вариантом, но в моем наборе было огромное количество дубликатов, поэтому с окончательным набором данных было легко работать. Возможно, кто-то найдет это полезным для скорости. (и переход на оболочку монго должен быть довольно простым.)

Помните, однако, что для работы с mongoimport вам придется переформатировать конечный файл, чтобы он содержал по 1 документу в строке. (Поиск / замена все должно быть хорошо здесь.)

0 голосов
/ 08 апреля 2015
/*
 * This map reduce will output a new collection: "duplicateinvoices"
 * { "_id" : "12345", "value" : 2 }
 * { "_id" : "23456", "value" : 2 }
 * ...
**/
m = function () { 
    emit(this.MlsId, 1); 
}

r = function (k, vals) { 
    return Array.sum(vals); 
} 

res = db.invoices.mapReduce(m,r, { out : "duplicateinvoices" });

/*
 * We have two approaches (we should test wich is faster/reliable, i didn't
**/

/* OPTION 1 */
// We iterate over duplicateinvoices and get the media-hash
// of the ones with value > 1 the duplicates
db.duplicateinvoices.find({value: {$gt: 1}}).forEach(
    function(invoice) {
        // temporary save one of this objects into a variable
        var obj = db.invoices.findOne({ media_hash: invoice._id });
        // remove all media-hash matched invoices from invoice collection
        db.invoices.remove({media_hash: invoice._id})
        // insert again the previously saved object into collection 
        db.invoices.insert(obj)
    }
)

/* OPTION 2 */
// We iterate over duplicateinvoices and get the media-hash
// of the ones with value > 1 the duplicates
db.duplicateinvoices.find({value: {$gt: 1}}).forEach(
    function(invoice) {
        // Invoices cursor with all the media_hash matched documents
        var cur = db.invoices.find({ media_hash: invoice._id });
        var first = true;
        while (cur.hasNext()) {
            var doc = cur.next();
            // Skip the first one
            if (first) {first = false; continue;}
            // Delete the others matched documents
            db.userList.remove({ _id: doc._id });
        }
    }
)

Источники:

Как удалить дубликат записи в MongoDB с помощью MapReduce? http://openmymind.net/2011/1/20/Understanding-Map-Reduce/ http://docs.mongodb.org/manual/tutorial/map-reduce-examples/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...