CouchDB View - 在分组之前过滤密钥

时间:2017-05-16 13:13:36

标签: mapreduce couchdb

我有一个CouchDB数据库,其中包含以下格式的文档:

array (
  0 => 
  array (
    'ab' => 'err',
    'bb' => 'iuierru',
    'eta' => 2,
  ),
  1 => 
  array (
    'ab' => 'jdgfd',
    'bb' => 'edfguiru',
    'eta' => 2,
  ),
  2 => 
  array (
    'ab' => 'fdfdf',
    'bb' => 'dfdf',
    'eta' => 3,
  ),
  3 => 
  array (
    'ab' => 'jdfd',
    'bb' => 'iweuiru',
    'eta' => 4,
  ),
  4 => 
  array (
    'ab' => 'dsdjdfd',
    'bb' => 'iuiru',
    'eta' => 5,
  ),
)

我想写一个视图,它会告诉我哪个用户创建了多少种类型的操作。我能够创建一个执行此操作的视图:

{ createdBy: 'userId', at: 123456, type: 'action_type' }

使用reduce函数“sum”并以这种方式使用视图:

/ _设计/ userActionsDoc / _view / userActions?group_level = 2

这会以我想要的方式返回行结果:

 emit([doc.createdBy, doc.type, doc.at], 1);
问题是现在我想过滤给定时间段的结果。所以我希望得到完全相同的信息,但只考虑在给定时间段内发生的行为。

如果我以不同的顺序发出字段,我可以通过“at”过滤文档。

group_level = 3&安培; startkey = [149328316160]&安培; endkey = [1493283161647,{},{}]

"rows":[ {"key":["userId","ACTION_1"],"value":20}, ...

但是我不会得到按userId和actionType分组的结果。有两种方法可以同时拥有吗?也许写自己的reduce函数?

1 个答案:

答案 0 :(得分:1)

我感觉到你的痛苦。我过去做过两件不同的事情试图解决类似的问题。

第一种模式是痛苦,可能效果很好或者可能根本不起作用。我经历过这两个。你的map函数看起来像这样:

function(doc) {
  var obj = {};
  obj[doc.createdBy] = {};
  obj[doc.createdBy][doc.type] = 1;

  emit(doc.at, obj);
  // Ignore this for now
  // emit(doc.at, JSON.stringify(obj));
}

然后你的reduce函数如下所示:

function(key, values, rereduce) {
  var output = {};
  values.forEach(function(v) {
    // Ignore this for now
    // v = JSON.parse(v);
    for (var user in v) {
      for (var action in v[user]) {
        output[user][action] = (output[user][action] || 0) + v[user][action];
      }
    }
  });
  return output;
  // Ignore this for now
  // return JSON.stringify(output);
}

对于大型数据集,这通常会导致沙发错误,表明您的reduce函数没有足够快地缩小。在这种情况下,您可以对代码进行字符串化/解析,如代码中的“忽略”注释所示。

这背后的原因是couchdb最终希望您在reduce函数中输出一个简单的对象,如字符串或整数。根据我的经验,字符串变长似乎并不重要,只要它仍然是一个字符串。如果输出一个对象,在某些时候函数会出错,因为你为该对象添加了太多的道具。

第二种模式可能更好,但要求您的时间段提前“定义”。如果您的时间段要求可以锁定到特定年份,特定月份,日期,季度等。您只需在地图功能中多次发出。下面我假设at属性是纪元毫秒,或者至少是日期构造函数可以准确解析的东西。

function(doc) {
  var time_key;
  var my_date = new Date(doc.at);

  //// Used for filtering results in a given year 
  //// e.g. startkey=["2017"]&endkey=["2017",{}]
  time_key = my_date.toISOString().substr(0,4);
  emit([time_key, doc.createdBy, doc.type], 1);

  //// Used for filtering results in a given month
  //// e.g. startkey=["2017-01"]&endkey=["2017-01",{}]
  time_key = my_date.toISOString().substr(0,7);
  emit([time_key, doc.createdBy, doc.type], 1);

  //// Used for filtering results in a given quarter
  //// e.g. startkey=["2017Q1"]&endkey=["2017Q1",{}]
  time_key = my_date.toISOString().substr(0,4) + 'Q' + Math.floor(my_date.getMonth()/3).toString();
  emit([time_key, doc.createdBy, doc.type], 1);
}

然后,您的reduce功能与原始功能相同。基本上,您只是尝试为键中与定义的时间段对应的第一项定义常量值。适用于业务报告,但不能用于允许灵活的时间段。