MongoDB : サルでもわかるMapReduceその2:ログの時間別集計を行う


RDBMSでは下記のようなSQLになると思います。(PostgreSQLでの例)
SELECT
  to_char(timestamp, 'yyyy-mm-dd HH24'::text) AS ymdh  ,count(*)
FROM accesslog
WHERE  '2011-12-01' <= timestamp AND timestamp < '2011-12-02'
GROUP BY ymdh
ORDER BY ymdh
;

MapReduceを実行する

// 日本標準時を扱うためのユーティリティ関数

var JSTDate = function (str) {  return ISODate(str + "T00+09:00");  };


// 日付範囲指定で対象を絞る。
// このように一時変数に記憶させておくと便利。

var query  = { "timestamp" : { "$gte" : JSTDate("2011-11-01"), "$lt" : JSTDate("2011-11-02") } };


// map関数を定義。

var m = function () {


   var getYMDH = function (d) {

      d.setSeconds(0);
      d.setMilliseconds(0);
      d.setMinutes(0);

      yy = d.getFullYear();
      mm = d.getMonth() + 1;
      dd = d.getDate();
      hh = d.getHours();

      if (mm < 10) { mm = "0" + mm; }
      if (dd < 10) { dd = "0" + dd; }
      if (hh < 10) { hh = "0" + hh; }
      return yy + '-' + mm + '-' + dd + ' ' + hh + ':00:00';
   };

   emit(getYMDH(this.timestamp), {count:1});
};


// reduce関数を定義。

var r = function(key,values) {
    var result = { count: 0};
    values.forEach(function(value){
      result.count += value.count;
    });
    return result;
};


// mapReduceを実行。
// 結果は'myresults'というコレクションに保存されます。

db.accesslog.mapReduce(m,r, { query : query , out : 'myresults' } );


// 結果をみる

db.myresults.find();
{ "_id" : "2011-11-01 00:00:00", "value" : { "count" : 7546 } }
{ "_id" : "2011-11-01 01:00:00", "value" : { "count" : 2885 } }
{ "_id" : "2011-11-01 02:00:00", "value" : { "count" : 823 } }
{ "_id" : "2011-11-01 03:00:00", "value" : { "count" : 823 } }
{ "_id" : "2011-11-01 04:00:00", "value" : { "count" : 590 } }
{ "_id" : "2011-11-01 05:00:00", "value" : { "count" : 579 } }
{ "_id" : "2011-11-01 06:00:00", "value" : { "count" : 1365 } }
{ "_id" : "2011-11-01 07:00:00", "value" : { "count" : 3230 } }
{ "_id" : "2011-11-01 08:00:00", "value" : { "count" : 4344 } }
{ "_id" : "2011-11-01 09:00:00", "value" : { "count" : 5754 } }
{ "_id" : "2011-11-01 10:00:00", "value" : { "count" : 8066 } }
{ "_id" : "2011-11-01 11:00:00", "value" : { "count" : 9099 } }
{ "_id" : "2011-11-01 12:00:00", "value" : { "count" : 12266 } }
{ "_id" : "2011-11-01 13:00:00", "value" : { "count" : 12504 } }
{ "_id" : "2011-11-01 14:00:00", "value" : { "count" : 7752 } }
{ "_id" : "2011-11-01 15:00:00", "value" : { "count" : 7876 } }
{ "_id" : "2011-11-01 16:00:00", "value" : { "count" : 6843 } }
{ "_id" : "2011-11-01 17:00:00", "value" : { "count" : 6197 } }
{ "_id" : "2011-11-01 18:00:00", "value" : { "count" : 5069 } }
{ "_id" : "2011-11-01 19:00:00", "value" : { "count" : 8490 } }
has more

it
{ "_id" : "2011-11-01 20:00:00", "value" : { "count" : 10966 } }
{ "_id" : "2011-11-01 21:00:00", "value" : { "count" : 17013 } }
{ "_id" : "2011-11-01 22:00:00", "value" : { "count" : 18394 } }
{ "_id" : "2011-11-01 23:00:00", "value" : { "count" : 15914 } }
ざっとこんな感じです。

追記

もうちょっと簡単に書けることがわかりました。
今回のような単純な件数カウント処理の場合は、map/reduceのvalueをただの数値にしてもよいでしょう。

さらに、Array.sum()という組み込みメソッドを使えばreduceは1行でいけます。

var m = function () {

   var getYMDH = function (d) {

     d.setSeconds(0);
     d.setMilliseconds(0);
     d.setMinutes(0);

     yy = d.getFullYear();
     mm = d.getMonth() + 1;
     dd = d.getDate();
     hh = d.getHours();

     if (mm < 10) { mm = "0" + mm; }
     if (dd < 10) { dd = "0" + dd; }
     if (hh < 10) { hh = "0" + hh; }
     return yy + '-' + mm + '-' + dd + ' ' + hh + ':00:00';
   };

   emit(getYMDH(this.timestamp), 1);
};


var r = function(key,values) {
    return Array.sum(values);
};

db.accesslog.mapReduce(m,r, { query : query , out : 'myresults' } );

db.myresults.find();
{ "_id" : "2011-11-01 00:00:00", "value" : 7546 }
{ "_id" : "2011-11-01 01:00:00", "value" : 2885 }
{ "_id" : "2011-11-01 02:00:00", "value" : 823 }
{ "_id" : "2011-11-01 03:00:00", "value" : 823 }
{ "_id" : "2011-11-01 04:00:00", "value" : 590 }
{ "_id" : "2011-11-01 05:00:00", "value" : 579 }
{ "_id" : "2011-11-01 06:00:00", "value" : 1365 }
{ "_id" : "2011-11-01 07:00:00", "value" : 3230 }
{ "_id" : "2011-11-01 08:00:00", "value" : 4344 }
カテゴリ: