Слияние двух коллекций в MongoDB - PullRequest
19 голосов
/ 14 марта 2012

Я пытался использовать MapReduce в MongoDB, чтобы сделать то, что я считаю простой процедурой.Я не знаю, является ли это правильным подходом, если я должен даже использовать MapReduce.Я гуглил, какие ключевые слова я придумал, и пытался попасть в документы, где, как я думал, я добился наибольшего успеха - но ничего.Может быть, я слишком много думаю об этом?

У меня есть две коллекции: details и gpas

details состоит из целой пачки документов (3+ миллиона).Элемент studentid может повторяться два раза, по одному для каждого year, как показано ниже:

{ "_id" : ObjectId("4d49b7yah5b6d8372v640100"), "classes" : [1,17,19,21], "studentid" : "12345a", "year" : 1}
{ "_id" : ObjectId("4d76b7oij7s2d8372v640100"), "classes" : [2,12,19,22], "studentid" : "98765a", "year" : 1}
{ "_id" : ObjectId("4d49b7oij7s2d8372v640100"), "classes" : [32,91,101,217], "studentid" : "12345a", "year" : 2}
{ "_id" : ObjectId("4d76b7rty7s2d8372v640100"), "classes" : [1,11,18,22], "studentid" : "24680a", "year" : 1}
{ "_id" : ObjectId("4d49b7oij7s2d8856v640100"), "classes" : [32,99,110,215], "studentid" : "98765a", "year" : 2}
...

gpas имеет элементы с одинаковыми studentid с details.Только одна запись на studentid, например:

{ "_id" : ObjectId("4d49b7yah5b6d8372v640111"), "studentid" : "12345a", "overall" : 97, "subscore": 1}
{ "_id" : ObjectId("4f76b7oij7s2d8372v640213"), "studentid" : "98765a", "overall" : 85, "subscore": 5}
{ "_id" : ObjectId("4j49b7oij7s2d8372v640871"), "studentid" : "24680a", "overall" : 76, "subscore": 2}
...

В конце я хочу получить коллекцию с одной строкой для каждого студента в следующем формате:

{ "_id" : ObjectId("4d49b7yah5b6d8372v640111"), "studentid" : "12345a", "classes_1": [1,17,19,21], "classes_2": [32,91,101,217], "overall" : 97, "subscore": 1}
{ "_id" : ObjectId("4f76b7oij7s2d8372v640213"), "studentid" : "98765a", "classes_1": [2,12,19,22], "classes_2": [32,99,110,215], "overall" : 85, "subscore": 5}
{ "_id" : ObjectId("4j49b7oij7s2d8372v640871"), "studentid" : "24680a", "classes_1": [1,11,18,22], "classes_2": [], "overall" : 76, "subscore": 2}
...

СпособЯ собирался сделать это, запустив MapReduce следующим образом:

var mapDetails = function() {
    emit(this.studentid, {studentid: this.studentid, classes: this.classes, year: this.year, overall: 0, subscore: 0});
};

var mapGpas = function() {
    emit(this.studentid, {studentid: this.studentid, classes: [], year: 0, overall: this.overall, subscore: this.subscore});
};

var reduce = function(key, values) {
    var outs = { studentid: "0", classes_1: [], classes_2: [], overall: 0, subscore: 0};

    values.forEach(function(value) {
        if (value.year == 0) {
            outs.overall = value.overall;
            outs.subscore = value.subscore;
        }
        else {
            if (value.year == 1) {
                outs.classes_1 = value.classes;
            }
            if (value.year == 2) {
                outs.classes_2 = value.classes;
            }

            outs.studentid = value.studentid;
        }
    });

    return outs;

};

res = db.details.mapReduce(mapDetails, reduce, {out: {reduce: 'joined'}})
res = db.gpas.mapReduce(mapGpas, reduce, {out: {reduce: 'joined'}})

Но когда я запускаю его, это моя результирующая коллекция:

{ "_id" : "12345a", "value" : { "studentid" : "12345a", "classes_1" : [ ], "classes_2" : [ ], "overall" : 97, "subscore" : 1 } }
{ "_id" : "98765a", "value" : { "studentid" : "98765a", "classes_1" : [ ], "classes_2" : [ ], "overall" : 85, "subscore" : 5 } }
{ "_id" : "24680a", "value" : { "studentid" : "24680a", "classes_1" : [ ], "classes_2" : [ ], "overall" : 76, "subscore" : 2 } }

Я пропускаю массивы классов.

Кроме того, как в стороне, как мне получить доступ к элементам в результирующем элементе MapReduce value?Всегда ли MapReduce выводит на value или как вы его еще называете?

Ответы [ 2 ]

43 голосов
/ 15 марта 2012

Это похоже на вопрос, который задавался пользователям MongoDB в группах Google.
https://groups.google.com/group/mongodb-user/browse_thread/thread/60a8b683e2626ada?pli=1

Ответ ссылается на интерактивный учебник, который похож на ваш пример: http://tebros.com/2011/07/using-mongodb-mapreduce-to-join-2-collections/

Для получения дополнительной информации о MapReduce в MongoDB, пожалуйста, смотрите документацию: http://www.mongodb.org/display/DOCS/MapReduce

Кроме того, есть полезное пошаговое руководство о том, как работает операция MapReduce в разделе «Дополнительно» статьи MongoDB Cookbook под названием «Поиск максимальных и минимальных значений с версионными документами»: http://cookbook.mongodb.org/patterns/finding_max_and_min/

Простите, если вы уже прочитали некоторые из упомянутых документов. Я включил их для удобства других пользователей, которые могут читать этот пост, и новичков в использовании MapReduce в MongoDB

Важно, чтобы выходные данные операторов 'emit' в функциях Map совпадали с выходными данными функции Reduce. Если функция Map выводит только один документ, функция Reduce может вообще не работать, и тогда ваша выходная коллекция будет иметь несоответствующие документы.

Я немного изменил ваши операторы карты, чтобы выдавать документы в формате желаемого результата, с двумя отдельными массивами классов.
Я также переработал вашу инструкцию Reduction, чтобы добавить новые классы в массивы classes_1 и classes_2, только если они еще не существуют.

var mapDetails = function(){
    var output = {studentid: this.studentid, classes_1: [], classes_2: [], year: this.year, overall: 0, subscore: 0}
    if (this.year == 1) {
        output.classes_1 = this.classes;
    }
    if (this.year == 2) {
        output.classes_2 = this.classes;
    }
    emit(this.studentid, output);
};

var mapGpas = function() {
    emit(this.studentid, {studentid: this.studentid, classes_1: [], classes_2: [], year: 0, overall: this.overall, subscore: this.subscore});
};

var r = function(key, values) {
    var outs = { studentid: "0", classes_1: [], classes_2: [], overall: 0, subscore: 0};

    values.forEach(function(v){
        outs.studentid = v.studentid;
        v.classes_1.forEach(function(class){if(outs.classes_1.indexOf(class)==-1){outs.classes_1.push(class)}})
        v.classes_2.forEach(function(class){if(outs.classes_2.indexOf(class)==-1){outs.classes_2.push(class)}})

        if (v.year == 0) {
            outs.overall = v.overall;
            outs.subscore = v.subscore;
        }
    });
    return outs;
};

res = db.details.mapReduce(mapDetails, r, {out: {reduce: 'joined'}})
res = db.gpas.mapReduce(mapGpas, r, {out: {reduce: 'joined'}})

Выполнение двух операций MapReduce приводит к следующей коллекции, которая соответствует желаемому формату:

> db.joined.find()
{ "_id" : "12345a", "value" : { "studentid" : "12345a", "classes_1" : [ 1, 17, 19, 21 ], "classes_2" : [ 32, 91, 101, 217 ], "overall" : 97, "subscore" : 1 } }
{ "_id" : "24680a", "value" : { "studentid" : "24680a", "classes_1" : [ 1, 11, 18, 22 ], "classes_2" : [ ], "overall" : 76, "subscore" : 2 } }
{ "_id" : "98765a", "value" : { "studentid" : "98765a", "classes_1" : [ 2, 12, 19, 22 ], "classes_2" : [ 32, 99, 110, 215 ], "overall" : 85, "subscore" : 5 } }
>

MapReduce всегда выводит документы в виде {_id: "id", value: "value"} Более подробная информация о работе с поддокументами содержится в документе под названием «Точечная запись (охват объектов)»: http://www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29

Если вы хотите, чтобы вывод MapReduce отображался в другом формате, вам придется делать это программно в вашем приложении.

Надеюсь, это улучшит ваше понимание MapReduce и сделает вас на шаг ближе к созданию желаемой выходной коллекции. Удачи!

1 голос
/ 15 марта 2012

Вы не можете использовать m / r для этого, так как он предназначен только для одной коллекции.Чтение из более чем одной коллекции нарушит совместимость шардинга и поэтому не допускается.Вы можете делать то, что хотите, либо с новой структурой агрегации (2.1+), либо делать это внутри своего приложения.

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