Fusionando dos colecciones en MongoDB

He intentado usar MapReduce en MongoDB para hacer lo que creo que es un procedimiento simple. No sé si este es el enfoque correcto, si debería usar MapReduce. Busqué en Google las palabras clave en las que pensé e intenté acceder a los documentos donde pensé que tendría más éxito, pero nada. Tal vez estoy pensando demasiado sobre esto?

Tengo dos colecciones: details y gpas

details se componen de un montón de documentos (3+ millones). El elemento studentid se puede repetir dos veces, una para cada year , como la siguiente:

 { "_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 tiene elementos con el mismo studentid ‘s de los details . Solo una entrada por studentid , como esta:

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

Al final quiero tener una colección con una fila para cada estudiante en este formato:

 { "_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 forma en que iba a hacer esto era ejecutar MapReduce de esta manera:

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

Pero cuando lo ejecuto, esta es mi colección resultante:

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

Me faltan las matrices de clases.

Además, como un aparte, ¿cómo accedo a los elementos en el elemento de value MapReduce resultante? ¿MapReduce siempre da como resultado value o lo que sea que lo nombre?

Esto es similar a una pregunta que se hizo en los Grupos de Google de usuarios de MongoDB.
https://groups.google.com/group/mongodb-user/browse_thread/thread/60a8b683e2626ada?pli=1

La respuesta hace referencia a un tutorial en línea que se parece a tu ejemplo: http://tebros.com/2011/07/using-mongodb-mapreduce-to-join-2-collections/

Para obtener más información sobre MapReduce en MongoDB, consulte la documentación: http://www.mongodb.org/display/DOCS/MapReduce

Además, hay un tutorial paso a paso útil de cómo funciona una operación de MapReduce en la sección “Extras” del artículo de MongoDB Cookbook titulado, “Búsqueda de valores máximos y mínimos con documentos versionados”: http: //cookbook.mongodb. org / patterns / finding_max_and_min /

Perdóname si ya has leído algunos de los documentos a los que se hace referencia. Los he incluido para el beneficio de otros usuarios que pueden estar leyendo esta publicación y nuevos para usar MapReduce en MongoDB

Es importante que las salidas de las declaraciones ’emitir’ en las funciones de Mapa coincidan con las salidas de la función Reducir. Si solo hay un documento generado por la función de Mapa, es posible que la función Reducir no se ejecute en absoluto, y luego su colección de resultados tendrá documentos que no coincidan.

He modificado ligeramente las declaraciones del mapa para emitir documentos en el formato de su salida deseada, con dos matrices de “clases” separadas.
También he reelaborado su statement de reducción para agregar nuevas clases a las matrices classes_1 y classes_2, solo si aún no existen.

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

Ejecutar los dos resultados de las operaciones de MapReduce en la siguiente colección, que coincide con su formato deseado:

 > 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 siempre genera documentos en forma de {_id: “id”, value: “value”}. Hay más información disponible sobre cómo trabajar con sub-documentos en el documento titulado, “Notación de puntos (Alcanzar objetos)”: http: / /www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29

Si desea que la salida de MapReduce aparezca en un formato diferente, deberá hacerlo programáticamente en su aplicación.

Con suerte, esto mejorará tu comprensión de MapReduce y te acercará un paso más a la producción de tu colección de resultados deseada. ¡Buena suerte!

No puede usar m / r para esto, ya que está diseñado para aplicarse solo en una colección. Leer de más de una colección romperá la compatibilidad de fragmentación y, por lo tanto, no está permitido. Puede hacer lo que quiera con el nuevo marco de agregación (2.1+) o hacerlo dentro de su aplicación.