Reputation: 1583
I'm trying to aggregate some data I have in CouchDB. There are photo documents in the database and rating documents in the database. Each rating document looks like so (sans _id and _rev):
{
"type": "rating",
"rating": 3 // Integer values are identifiers that map to a string; e.g 1 might mean 'funny'
"photo": "as9i83ufsafa09uj" // The id of the photo that this rating points to.
}
What I want to do is a get a count of each rating type for each photo.
{
"key": "as9i83ufsafa09uj", "value": [1, 7, 8, 6] // 1 '0' rating, 7 '1' ratings, etc.
"key": "photoid2", "value": [3, 0, 0, 8]
}
I'm using CouchDB views' MapReduce to achieve this aggregation.
"map": "function(doc) {
if(doc.type == 'rating')
{
emit(doc.photo, doc.rating);
}
}",
"reduce": "function(keys, values, rereduce) {
var result = new Array(0, 0, 0, 0);
values.forEach( function(key, value)
{
result[value]+=1;
});
return result;
}"
The map returns:
{"total_rows":55,"offset":0,"rows":[
{"id":"0aa2c4c9a031eedbcf2795cabc1679be","key":"4aa5ec26-26b8-490a-a9cc-620a0d2136b9","value":0},
{"id":"29f363432e008f5934b4160292e18680","key":"4aa5ec26-26b8-490a-a9cc-620a0d2136b9","value":3},
{"id":"646d0d764623bc2f3ed1354ac03b583e","key":"4aa5ec26-26b8-490a-a9cc-620a0d2136b9","value":2},
...
{"id":"fa5be78402171e3bf1eb1cf91c5fda6e","key":"c63b78b6-ad92-426c-ab64-c9a6ae229b31","value":1}
]}
The reduce returns witb group_level=0:
{"rows":[
{"key":null,"value":[1,1,1,1]}
]}
with group_level=1:
{"rows":[
{"key":"4aa5ec26-26b8-490a-a9cc-620a0d2136b9","value":[2,2,2,0]},
{"key":"5ad3de4b-d25b-42d3-95e0-df7661becbf3","value":[2,2,2,2]},
{"key":"7600710b-9ae3-4312-876c-ad352722dac3","value":[2,2,2,2]},
{"key":"959f48a2-5018-4938-aab4-086d8824dd75","value":[2,0,0,0]},
{"key":"c63b78b6-ad92-426c-ab64-c9a6ae229b31","value":[2,2,2,2]}
]}
I'm familiar with MongoDB's map reduce and this function would work using their schema. What do I need to tweak for this to work in CouchDB?
UPDATE This is the final reduce function that worked for me. I wasn't addressing the rereduce parameter correctly. Thanks goes to Marcin Skórzewski for helping me to better understand rereduce.
"reduce": "function(key, values, rereduce) {
var result = new Array(0, 0, 0, 0);
if(rereduce == true)
{
for(var i = 0; i < values.length; i++)
{
var value = values[i];
for (var j = 0; j < value.length; j++)
{
result[j] += value[j];
}
}
return result;
}
for(var i = 0; i < values.length; i++)
{
value = values[i];
result[value]+=1;
}
return result;
}"
Upvotes: 4
Views: 2484
Reputation: 2854
I think you are not using rereduce
properly. The values
element and returned data is not the same type. In case of only one group level and small size of data (to fit in the single B-tree node) it could work fine, because no rereduce has to be run. Take a look at the reduce doc and the meaning of rereduce
argument for reduce()
.
In case reduce()
is run for values emitted from map()
they are integers, but if you reduce values obtained by prior reduce()
they are arrays. You can make use of the rereduce
and in case it is true
add arrays. Or you can emit arrays in map (e.g. to get [0,0,0,1]
instead of 3
) and always add arrays in reduce()
without worrying about rereduce
argument.
Upvotes: 4