Alex
Alex

Reputation: 38499

CouchDB view - reduce / group duplicate key values to array

I've got a view on my couch db, which outputs data in this format:

{"rows":[
{"key":["Partner1","Voucher Type 1"],"value":true},
{"key":["Partner1","Voucher Type 2"],"value":true},
{"key":["Partner2","Voucher Type 1"],"value":true},
{"key":["Partner3","Voucher Type 1"],"value":true},
{"key":["Partner4","Voucher Type 1"],"value":true}
]}

What I'm trying to get do is effectively 'group' the Partner | Voucher Type, So in the example above, It would return be something like:

Partner1: ["Voucher Type 1", "Voucher Type 2"]
Partner2: ["Voucher Type 1"]
Partner3: ["Voucher Type 1"]
Partner4: ["Voucher Type 1"]

Currently, my map reduce functions look like this:

Map:

function(
    emit([doc.PartnerName, doc.VoucherType], 1);
}

Reduce:

function(keys, values) {
    return true;
}

I'm querying with group=true

I suspect I need to do more in the reduce function?

Upvotes: 3

Views: 9754

Answers (3)

OrangeDog
OrangeDog

Reputation: 38749

Your aim is not to reduce the amount of data, only to change the format. So do not use a reduce function, use a list function.

function(head, req) {
    var lastKey, row, dedup;

    while (row = getRow()) {
        if (row.key !== lastKey) {
            dedup = {};
            send('\n' + row.key + ': ');
        }

        if (!dedup[row.value]) {
            if (row.key === lastKey) {
                send(', ');
            }

            dedup[row.value] = true;
            send(row.value);
        }

        lastKey = row.key;
    }
}

This one just gives you a plaintext listing, but you can add whatever formatting you desire, e.g. JSON.

Partner1: Voucher Type 1, Voucher Type 2
Partner2: Voucher Type 1

If you don't need the de-duplication, then it's even simpler.

Upvotes: 2

Alex
Alex

Reputation: 38499

I got it working by using the following reduce:

function(keys, values, rereduce){
  var item = {};
  r=[];

  values.forEach(function(value){
    item[value] = value;
  });

  for(var i in item){
    r.push(item[i]);
  }

  return r; 
}

Comments welcome if this is incorrect, but it's returning me the data in the desired form:

PartnerName: VoucherType[]

Upvotes: 1

brdlph
brdlph

Reputation: 619

Consider the following design document:

{
   "_id": "_design/ddoc",
   "views": {
       "partners": {
           "map": function(doc) {
                      emit(doc.PartnerName, doc.VoucherType);
                  },
           "reduce": function(keys, values) {
                         var voucherTypes = [];
                         values.forEach(function(v) {
                             voucherTypes = voucherTypes.concat(v);
                         });
                         return voucherTypes;
                     }
       }
   }
}

What you could do is to make use of the reduce function with the group=true parameter, i.e.

<couchdb>/<database>/_design/ddoc/_view/partners?group=true

which would give you something like this:

{"rows":[
{"key":"Partner1","value":["Voucher Type 2","Voucher Type 1"]},
{"key":"Partner2","value":["Voucher Type 1"]},
{"key":"Partner3","value":["Voucher Type 2"]}
]}

However, this is rather discouraged because you are building up data structures in a reduce function. Reduce functions should return simple, usually numeric values. Also, the above reduce function might break in a rereduce case. I have not tested this. As an alternative I can propose to realize the query only with the map function, that is

<couchdb>/<database>/_design/ddoc/_view/partners?reduce=false&key="Partner1"

which would return:

{"total_rows":4,"offset":0,"rows":[
{"id":"97c7ee4d90f57407bb1f4f680d20967b","key":"Partner1","value":"Voucher Type 1"},
{"id":"97c7ee4d90f57407bb1f4f680d20a049","key":"Partner1","value":"Voucher Type 2"}
]}

Upvotes: 1

Related Questions