我感觉到你的痛苦。我过去做过两件不同的事情来尝试解决类似的问题。
第一种模式很痛苦,可能效果很好,也可能根本不起作用。两种我都经历过。您的地图功能如下所示:
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 函数收缩得不够快。在这种情况下,您可以对对象进行字符串化/解析,如代码中的“忽略”cmets 所示。
这背后的原因是 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 功能与您原来的功能相同。本质上,您只是尝试为键中的第一项定义一个常量值,该值对应于定义的时间段。非常适用于业务报告,但不太适合灵活的时间段。