Suppose, I have three document types:
{ _id: "1234", type: "category" }
{ _id: "5678", type: "group", :category_id: "1234" }
{ _id: "9012", type: "item", :group_id: "5678" }
Now, to get all the items in a group, I'd have a view like so:
function(doc) {
if(doc.type == "item") {
map([doc.group_id, doc]);
}
}
and to get all the groups in a category:
function(doc) {
if(doc.type == "group") {
map([doc.category_id, doc]);
}
}
However, if I wanted to get all the items in a category, I'd need to
interrogate the db for the list of groups and then grab the list of
items for each group. Is that the only way? I tried thinking of various
ways to map/reduce the data, or double-map the data, but consistently
came up blank.
I know that this could be solved easily by denormalising the data so
that the items stored their category id, but I thought I'd be stubborn
to figure out other ways to do it.
It would be possible if the reducer was no longer required to be a
combiner and could actually emit multiple documents from its data
however it wanted.
First map:
function(doc) {
if(doc.type == "item") {
map([doc.group_id, doc])
} else if (doc.type =="group") {
map([doc._id, doc])
}
}
Then a reduction of each key into new documents:
function(key, values) {
var group;
for(i=0; i < values.length; i++) {
if(values[i].type == "group") {
group = values[i];
}
}
if(group) {
for(i=0; i < values.length; i++) {
if(values[i].type == "item") {
map(group.category_id, item);
}
}
}
}
(and more advanced queries could go through further map/reduce cycles).
Damien's blog discussions on the map/reduce implementation seem to imply
that the reduce function should be reducing the values for a key down
into a single key,value pair, but Map/Reduce does not require that - as
shown in the example above, it's useful to be able to emit multiple
key/value pairs from the 'reduce', actually expanding the key/value set.
It is COMMON for the reduce function to emit a single value - it is not
necessary for Map/Reduce to be restricted in that way.
Dan.