Объединение двух коллекций в MongoDB
Я пытаюсь использовать 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
или что-то еще, что вы называете?
Ответы
Ответ 1
Это похоже на вопрос, заданный пользователям групп Google MongoDB.
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 в разделе "Дополнительно" в статье "Cookbook" MongoDB под названием "Поиск значений Max и Min с версиями":
http://cookbook.mongodb.org/patterns/finding_max_and_min/
Простите меня, если вы уже прочитали некоторые из ссылочных документов. Я включил их в интересах других пользователей, которые могут читать это сообщение, и новые для использования MapReduce в MongoDB
Важно, чтобы выходы из выражений 'emit' в функциях Map соответствовали выходам функции Reduce. Если в функции Map есть только один документ, функция Reduce может вообще не выполняться, и тогда ваша коллекция будет иметь несогласованные документы.
Я немного изменил ваши утверждения карты, чтобы исправить документы в формате желаемого вывода, с двумя отдельными массивами классов.
Я также переработал инструкцию сокращения, чтобы добавить новые классы в массивы 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 и поможет вам на один шаг приблизиться к созданию нужной коллекции. Удачи!
Ответ 2
Вы не можете использовать m/r для этого, поскольку он предназначен только для одного набора. Чтение из более чем одной коллекции нарушает совместимость с осколками и поэтому не допускается. Вы можете сделать то, что хотите, с новой структурой агрегации (2.1+) или сделать это внутри своего приложения.