MongoDB学習の旅12:MongoDB MapReduce

4733 ワード

MongDBのMapReduceはMySQLの「groupby」に相当するので、MongoDBでMap/Reduceを使って並列に「統計」するのは簡単です.
MapReduceを使用して2つの関数Map関数とReduce関数を実装するには、Map関数はemit(key,value)を呼び出し、collectionのすべてのレコードを巡り、keyとvalueをReduce関数に渡して処理します.Map関数とReduce関数はJSで実現でき、dbで実現できる.runCommandまたはmapReduceコマンドは、MapReduce操作を実行します.
サンプルシェル
db.runCommand(
{ mapreduce : <collection>,
map : <mapfunction>,
reduce : <reducefunction>
[, query : <query filter object>]
[, sort : <sorts the input objects using this key. Useful for optimization, like sorting by the
emit key for fewer reduces>]
[, limit : <number of objects to return from collection>]
[, out : <see output options below>]
[, keeptemp: <true|false>]
[, finalize : <finalizefunction>]
[, scope : <object where fields go into javascript global scope >]
[, verbose : true]
}
);
パラメータの説明:
mapreduce:操作するターゲットの集合.
map:関数をマッピングします(reduce関数パラメータとしてキー値対シーケンスを生成します).
reduce:統計関数.
Query:ターゲットレコードフィルタリング.
sort:ターゲットレコードのソート.
Limit:ターゲットレコードの数を制限します.
out:統計結果はコレクションを格納します(指定しない場合は一時コレクションを使用し、クライアントが切断された後に自動的に削除します).
keeptemp:一時集合を保持するかどうか.
finalize:最終処理関数(reduce戻り結果を最終整理して結果セットに格納する).
scope:map、reduce、finalizeに外部変数をインポートします.
verbose:詳細な時間統計を表示します.
次に、後述の例に必要なデータを準備します.
> db.students.insert({classid:1, age:14, name:'Tom'})
> db.students.insert({classid:1, age:12, name:'Jacky'})
> db.students.insert({classid:2, age:16, name:'Lily'})
> db.students.insert({classid:2, age:9, name:'Tony'})
> db.students.insert({classid:2, age:19, name:'Harry'})
> db.students.insert({classid:2, age:13, name:'Vincent'})
> db.students.insert({classid:1, age:14, name:'Bill'})
> db.students.insert({classid:2, age:17, name:'Bruce'})
>
現在、1クラスと2クラスの学生数を統計する方法を示しています.
Map関数は、emit(key,value)を呼び出してキー値ペアを返し、thisを使用して現在処理されているDocumentにアクセスする必要があります.
ここはきっと忘れられない!!!
> m = function() { emit(this.classid, 1) }
function () {
emit(this.classid, 1);
}
>
valueは、JSON Objectを使用して渡すことができます(複数の属性値がサポートされています).例:
    emit(this.classid, {count:1})
Reduce関数が受け取るパラメータはGroup効果と似ており、Mapが返すキー値シーケンス群を{key,[value 1,value 2,value 3,value...]}に合成する.reduceに渡します.
> r = function(key, values) {
... var x = 0;
... values.forEach(function(v) { x += v });
... return x;
... }
function (key, values) {
var x = 0;
values.forEach(function (v) {x += v;});
return x;
}
>
Reduce関数はこれらのvaluesに対して“統計”の操作を行って、結果を返してJSON Objectを使うことができます.
結果は次のとおりです.
> res = db.runCommand({
... mapreduce:"students",
... map:m,
... reduce:r,
... out:"students_res"
... });
{
"result" : "students_res",
"timeMillis" : 1587,
"counts" : {
"input" : 8,
"emit" : 8,
"output" : 2
},
"ok" : 1
}
> db.students_res.find()
{ "_id" : 1, "value" : 3 }
{ "_id" : 2, "value" : 5 }
>
mapReduce()は、結果をstudents_resテーブルに格納します.
finalize()を用いてreduce()の結果をさらに処理することができる.
> f = function(key, value) { return {classid:key, count:value}; }
function (key, value) {
return {classid:key, count:value};
}
>
もう一度計算して、戻った結果を見てみましょう.
> res = db.runCommand({
... mapreduce:"students",
... map:m,
... reduce:r,
... out:"students_res",
... finalize:f
... });
{
"result" : "students_res",
"timeMillis" : 804,
"counts" : {
"input" : 8,
"emit" : 8,
"output" : 2
},
"ok" : 1
}
> db.students_res.find()
{ "_id" : 1, "value" : { "classid" : 1, "count" : 3 } }
{ "_id" : 2, "value" : { "classid" : 2, "count" : 5 } }
>
列名が「classid」と「count」に変わりました.このようなリストは理解しやすいです.
   
さらに制御の詳細を追加することもできます.
> res = db.runCommand({
... mapreduce:"students",
... map:m,
... reduce:r,
... out:"students_res",
... finalize:f,
... query:{age:{$lt:10}}
... });
{
"result" : "students_res",
"timeMillis" : 358,
"counts" : {
"input" : 1,
"emit" : 1,
"output" : 1
},
"ok" : 1
}
> db.students_res.find();
{ "_id" : 2, "value" : { "classid" : 2, "count" : 1 } }
>
は,先にフィルタリングを行い,age<10のデータのみを取り,その後統計を行うため,1クラスの統計データがないことが分かる.