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?
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!
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.