开发者

Selecting n elements belonging to a user in a MapReduce for CouchDB

Please bear with me, I'm开发者_StackOverflow pretty new to the whole CouchDb stuff.

The db looks like:

** item ** count ** user **
   A       20       bob
   B       30       bob
   C       10       bob
   D       15       john

I want to write a MapReduce that selects all the items belonging to bob and only return the top 2, sorted. so it should return [{item:"B",count:"30"},{item:"A",count:"20}]

I'm not sure how this can be done? Seems like I have to emit(doc.item, doc.count), but how do I know if the user owns the doc? How do I run another MapReduce to select the top elements?


One solution would be to write your view to use a complex key, such as:

function (doc) {
    emit([doc.user, doc.count], doc.item);
}

If you add descending=true to your query string, that would give you a view result like:

{"total_rows":4,"offset":0,"rows":[
    {"id":"53f359b7cd360da296dd9aab3d0029bd","key":["john",15],"value":"D"},
    {"id":"53f359b7cd360da296dd9aab3d001a0e","key":["bob",30],"value":"B"},
    {"id":"53f359b7cd360da296dd9aab3d000fec","key":["bob",20],"value":"A"},
    {"id":"53f359b7cd360da296dd9aab3d002668","key":["bob",10],"value":"C"}
]}

It's sorted already by user, then count. (with the item type as the value)

Then you can use a _list function to do the rest. The code below basically loops through the view, and returns the top 2 results for each user. If you specify user=bob in the query string, you'll only get the results for bob.

function (head, req) {
    // specify that we're sending JSON as our response
    provides('json', function () {
        var results = [],
            result, user, count, row;

        while (row = getRow()) {
            // if the user doesn't match the last iteration, reset our counter
            if (user != row.key[0]) {
                user = row.key[0];
                count = 0;
            }

            // we only need the top 2
            if (count++ >= 2) {
                continue;
            }

            // start building a result object
            result = {
                item: row.value,
                count: row.key[1]
            };

            // if we provide user=?
            if (req.query.user) {
                // check to see if it matches the current user
                if (req.query.user === user) {
                    // if so, add it to the results
                    results.push(result);
                }
            // by default, we'll return the top 2 for every user
            } else {
                // add the user key to the result object
                result.user = row.key[0];
                // and add it to the result set
                results.push(result);
            }
        }

        // send outside the loop, since it needs to be sent as valid JSON
        send(JSON.stringify(results));
    });
}


If you put user and count in the key of the view, you can use startkey=["bob",""] and endkey=["bob"] to select the user, and descending=true and limit=2 to get the top two items.

I tried the following map function:

function(doc) {
  if(doc.user && doc.count && doc.item) {
    emit([doc.user, doc.count], doc);
  }
}

with the query string ?startkey=["bob",""]&endkey=["bob"]&descending=true&limit=2 it returns:

{"total_rows":4,"offset":1,"rows":[
{"id":"item_B_bob","key":["bob",30],"value":{"_id":"item_B_bob","_rev":"1-b23bd22fb719c7d59b045bce0932df8c","item":"B","count":30,"user":"bob"}},
{"id":"item_A_bob","key":["bob",20],"value":{"_id":"item_A_bob","_rev":"2-515bca46eab383cfeaaa2a101d180291","item":"A","count":20,"user":"bob"}}
]}

Please note:

  • startkey and endkey are reversed because descending=true.
  • ["bob",""] is a key greater then ["bob", ANY NUMBER] as specified in view collation.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜