web-dev-qa-db-ja.com

MongoDBで2つのコレクションをマージする

MongoDBでMapReduceを使用して、簡単な手順だと思っていることを実行しようとしています。これが正しいアプローチであるかどうか、MapReduceを使用する必要があるかどうかはわかりません。私は自分が考えたキーワードをググって、自分が最も成功するだろうと思ったところにドキュメントをヒットしようとしましたが、何もしませんでした。多分私はこれについて一生懸命考えているのですか?

2つのコレクションがあります:detailsgpas

detailsは、大量のドキュメント(300万以上)で構成されています。 studentid要素は、次のように、yearごとに1つずつ、2回繰り返すことができます。

{ "_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には、studentiddetailsと同じ要素があります。次のように、studentidごとに1つのエントリのみ:

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

最後に、次の形式で各学生に1行のコレクションを作成します。

{ "_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などの名前を付けて出力しますか?

19
TFX

これは、MongoDB-users Googleグループで尋ねられた質問に似ています。
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/

MongoDBのMapReduceの詳細については、ドキュメントを参照してください。 http://www.mongodb.org/display/DOCS/MapReduce

さらに、MongoDBクックブックの「バージョン付きドキュメントによる最大値と最小値の検索」というタイトルの「補足」セクションに、MapReduce操作がどのように機能するかについて、ステップバイステップの便利なウォークスルーがあります。 http:// cookbook.mongodb.org/patterns/finding_max_and_min/

参照ドキュメントの一部を既に読んでいる場合は、ご容赦ください。この投稿を読んでいて、MongoDBでMapReduceを初めて使用するユーザーのために、これらを含めました。

Map関数の「emit」ステートメントからの出力が、Reduce関数の出力と一致することが重要です。 Map関数によって出力されるドキュメントが1つしかない場合、Reduce関数はまったく実行されない可能性があり、出力コレクションに不一致のドキュメントが含まれます。

2つの個別の「クラス」配列を使用して、目的の出力の形式でドキュメントを出力するように、マップステートメントを少し変更しました。
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'}})

2つの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"}の形式でドキュメントを出力します。「Dot Notation(Reaching into Objects)」というタイトルのドキュメントには、サブドキュメントの操作に関する詳細情報があります。 http://www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29

MapReduceの出力を別の形式で表示したい場合は、アプリケーションでプログラム的に行う必要があります。

うまくいけば、これによりMapReduceの理解が深まり、目的の出力コレクションの作成に一歩近づくことができます。幸運を!

43
Marc

M/rは1つのコレクションにのみ適用されるように設計されているため、これには使用できません。複数のコレクションから読み取ると、シャーディングの互換性が失われるため、許可されません。新しい集約フレームワーク(2.1以降)を使用するか、アプリケーション内でこれを行うことができます。

1
Remon van Vliet