web-dev-qa-db-fra.com

Fusion de deux collections dans MongoDB

J'ai essayé d'utiliser MapReduce dans MongoDB pour faire ce que je pense être une procédure simple. Je ne sais pas si c'est la bonne approche, si je devrais même utiliser MapReduce. J'ai recherché sur Google les mots clés auxquels j'ai pensé et j'ai essayé de trouver les documents où je pensais avoir le plus de succès - mais rien. Peut-être que je réfléchis trop à ça?

J'ai deux collections: details et gpas

details est composé de tout un tas de documents (plus de 3 millions). L'élément studentid peut être répété deux fois, un pour chaque year, comme suit:

{ "_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 a des éléments avec les mêmes studentid de details. Une seule entrée par studentid, comme ceci:

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

Au final, je veux avoir une collection avec une ligne pour chaque élève dans ce format:

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

La façon dont j'allais le faire était d'exécuter MapReduce comme ceci:

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

Mais quand je l'exécute, voici ma collection résultante:

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

Je manque les tableaux de classes.

Aussi, en passant, comment puis-je accéder aux éléments dans l'élément MapReduce value résultant? MapReduce génère-t-il toujours une sortie vers value ou tout autre nom que vous lui donnez?

19
TFX

Ceci est similaire à une question posée sur les groupes Google des utilisateurs de MongoDB.
https://groups.google.com/group/mongodb-user/browse_thread/thread/60a8b683e2626ada?pli=1

La réponse fait référence à un didacticiel en ligne qui ressemble à votre exemple: http://tebros.com/2011/07/using-mongodb-mapreduce-to-join-2-collections/

Pour plus d'informations sur MapReduce dans MongoDB, veuillez consulter la documentation: http://www.mongodb.org/display/DOCS/MapReduce

De plus, il existe une procédure pas à pas utile sur le fonctionnement d'une opération MapReduce dans la section "Extras" de l'article de MongoDB Cookbook intitulée "Finding Max And Min Values ​​with Versioned Documents": http: // cookbook.mongodb.org/patterns/finding_max_and_min/

Pardonnez-moi si vous avez déjà lu certains des documents référencés. Je les ai inclus pour le bénéfice d'autres utilisateurs qui liront peut-être cet article et qui découvrent l'utilisation de MapReduce dans MongoDB

Il est important que les sorties des instructions 'emit' des fonctions Map correspondent aux sorties de la fonction Reduce. S'il n'y a qu'un seul document sorti par la fonction Carte, la fonction Réduire n'est peut-être pas exécutée du tout, puis votre collection de sortie aura des documents incompatibles.

J'ai légèrement modifié vos instructions de carte pour émettre des documents au format de la sortie souhaitée, avec deux tableaux de "classes" distincts.
J'ai également retravaillé votre instruction de réduction pour ajouter de nouvelles classes aux tableaux classes_1 et classes_2, uniquement si elles n'existent pas déjà.

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

L'exécution des deux opérations MapReduce donne la collection suivante, qui correspond au format souhaité:

> 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 génère toujours des documents sous la forme de {_id: "id", valeur: "valeur"}. Il y a plus d'informations disponibles sur l'utilisation des sous-documents dans le document intitulé "Notation par points (atteindre les objets)":: http://www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29

Si vous souhaitez que la sortie de MapReduce apparaisse dans un format différent, vous devrez le faire par programme dans votre application.

Espérons que cela améliorera votre compréhension de MapReduce et vous rapprochera de la production de la collection de sortie souhaitée. Bonne chance!

43
Marc

Vous ne pouvez pas utiliser m/r pour cela car il est conçu pour ne s'appliquer qu'à une seule collection. La lecture de plusieurs collections interrompra la compatibilité du partage et n'est donc pas autorisée. Vous pouvez faire ce que vous voulez avec le nouveau cadre d'agrégation (2.1+) ou le faire dans votre application.

1
Remon van Vliet