Łączenie dwóch kolekcji w MongoDB

Próbowałem użyć MapReduce w MongoDB, aby zrobić to, co uważam za prostą procedurę. Nie wiem, czy jest to właściwe podejście, czy powinienem w ogóle używać MapReduce. Wygooglowałem słowa kluczowe, które wymyśliłem i próbowałem trafić do dokumentów, w których myślałem, że odniosę największy sukces - ale nic. Może za bardzo o tym myślę?

Mam dwa zbiory: details i gpas

details składa się z całej masy dokumentów( 3 + mln). Element studentid może być powtórzone dwa razy, po jednym dla każdego year, jak poniżej:

{ "_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 ma elementy o tych samych studentid ' S z details. Tylko jeden wpis na studentid, tak:

{ "_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}
...

Na koniec chcę mieć kolekcję z jednym wierszem dla każdego ucznia w tym formacie:

{ "_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}
...

Sposób, w jaki zamierzałem to zrobić, polegał na uruchomieniu MapReduce w taki sposób:

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'}})

Ale kiedy go uruchamiam, to jest moja kolekcja wynikowa:

{ "_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 } }

Brakuje mi zajęć tablice.

Poza tym, jak uzyskać dostęp do elementów w wynikowym elemencie MapReduce value? Czy MapReduce zawsze wyświetla value czy jak to inaczej nazwiesz?

Author: TFX, 2012-03-14

2 answers

To jest podobne do pytania, które zostało zadane na MongoDB-użytkownicy Google Groups.
https://groups.google.com/group/mongodb-user/browse_thread/thread/60a8b683e2626ada?pli=1

Odpowiedź odwołuje się do samouczka on-line, który wygląda podobnie do twojego przykładu: http://tebros.com/2011/07/using-mongodb-mapreduce-to-join-2-collections/

Aby uzyskać więcej informacji na temat MapReduce w MongoDB, zobacz dokumentacja: http://www.mongodb.org/display/DOCS/MapReduce

Dodatkowo, w sekcji "Dodatki" artykułu MongoDB Cookbook zatytułowanego "znajdowanie wartości Max I Min z Wersjonowanymi dokumentami znajduje się przydatna instrukcja krok po kroku, jak działa operacja MapReduce": http://cookbook.mongodb.org/patterns/finding_max_and_min/

Wybacz, jeśli przeczytałeś już niektóre dokumenty. Dodałem je z korzyścią dla innych użytkowników kto może czytać ten post i jest nowym użytkownikiem MapReduce w MongoDB

Ważne jest, aby wyjścia instrukcji 'emit' w funkcjach Map były zgodne z wyjściami funkcji Reduce. Jeśli funkcja mapy wyświetla tylko jeden dokument, funkcja zmniejsz może w ogóle nie być uruchomiona, a Twoja kolekcja wyjściowa będzie miała niedopasowane dokumenty.

Lekko zmodyfikowałem Twoje deklaracje map, aby emitowały dokumenty w formacie pożądanego wyniku, z dwoma oddzielne tablice "klas".
Przerobiłem również twoje polecenie reduce, aby dodać nowe klasy do tablic classes_1 i classes_2, tylko jeśli jeszcze nie istnieją.

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'}})

Uruchomienie dwóch operacji MapReduce daje następującą kolekcję, która odpowiada żądanemu formatowi:

> 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 zawsze wypisuje dokumenty w postaci {_id:"id", value: "value"} Więcej informacji na temat pracy z podobnymi dokumentami można znaleźć w dokumencie zatytułowanym " notacja kropkowa (Sięganie do obiektów)": http://www.mongodb.org/display/DOCS/Dot + notacja + %28reaching + do + obiektów%29

Jeśli chcesz, aby wynik MapReduce był wyświetlany w innym formacie, musisz to zrobić programowo w swojej aplikacji.

Mam nadzieję, że poprawi to twoje zrozumienie MapReduce i zbliży Cię o krok do stworzenia pożądanej kolekcji wyjściowej. Powodzenia!

 42
Author: Marc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-15 16:02:37

Nie można używać m / r do tego, ponieważ jest on przeznaczony tylko do jednego zbioru. Odczyt z więcej niż jednej kolekcji spowoduje zerwanie kompatybilności shardingu i dlatego nie jest dozwolony. Możesz robić, co chcesz, korzystając z nowej struktury agregacji (2.1+) lub wewnątrz aplikacji.

 2
Author: Remon van Vliet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-15 12:00:41