CouchApp: how to use views map/reduce


There is a JavaScript embedded in CouchDB: the one that is used in Futon. Lets see how to use it from your CouchApp for executing map/reduce views. This post is a follow on of this one.

Executing a view using jquery.couchdb.js

Background

Invoking a view is done by using:

db.view("design/view", {
    success:function (data) {
        // Got response from CouchDB
        // Process data
        // and then return retrieved data
    },
    error  :function (status) {
        // Error function...
    }
});

Where design is the CouchDB design document storing the view (view).

Using a key in a map function

Lets consider a CouchDB database with cities and states from USA stored in documents with the following structure:

{
   "_id": "0254D19B-7360-459E-A9F2-715E36AD1822",
   "_rev": "1-d6c3ca78003b4116ecef930d538b2d94",
   "type": "cities",
   "city": "Pasadena",
   "state": "California"
},
{
   "_id": "05C7AD05-0CC6-4C6E-A21B-E4C8344C242A",
   "_rev": "1-e0a64cd86be76166d3b1592376b8c23c",
   "type": "cities",
   "city": "Lakewood",
   "state": "Colorado"
}

I.e., Documents with field type value cities that allows me to isolate records containing the pairs (city – state) from others stored in the same database; and the the name of the city and the state itself.

Then I have defined the following map function:

function(doc) {
    if (doc.type==="cities") emit(doc.state, doc.city );
}

That emits every document of type cities. These emitted records will have as key the name of the state and as value, the name of the city.

If I execute the view using:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    }
});

Where post20 is the design document and getCities is the name of the view.

I get something like:

{
    "total_rows": 285, 
    "offset": 0,
    "rows": [
        {"id":"34716A25-2D33-43F7-BDA2-CBC063BC04A4", "key":"Alabama", "value":"Mobile"},
        {"id":"4F5D34B0-8F24-4C84-98D3-6F104EB19C42", "key":"Alabama", "value":"Montgomery"},
        {"id":"B3BB27B0-9D6E-4776-A685-67378E9198A3", "key":"Alabama", "value":"Birmingham"},
        {"id":"91B00C11-6648-4E15-83F2-957561ACE157", "key":"Alaska", "value":"Anchorage"},
        {"id":"212EA2DA-40C3-4443-AFB7-91366891D3D5", "key":"Arizona", "value":"Tucson"},
        {"id":"7132A69F-B1CE-4AB6-ABF8-22EC6745C35D", "key":"Arizona", "value":"Phoenix"},
        {"id":"9938B89F-E119-47AA-9186-F15BDEF557E1", "key":"Arizona", "value":"Surprise"},
        {"id":"A2E9C9F5-033F-454D-8B54-3A303F2E13C2", "key":"Arizona", "value":"Peoria"},
        {"id":"BFD01743-2297-47B1-B997-4B63B9BF4213", "key":"Arizona", "value":"Mesa"},
        ...
}

But, I want to choose only the cities from California then I have to set California as key (and that’s why I defined the map function that emits the state as key. To send the key to CouchDB I have to say:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    key    :"California"
});

and I get as result:

{
    "total_rows":285,
    "offset"    :16,
    "rows"      :[
        {"id":"0254D19B-7360-459E-A9F2-715E36AD1822", "key":"California", "value":"Pasadena"},
        {"id":"07A64E55-2AB1-4AA2-81E8-3D1D54508A26", "key":"California", "value":"Vallejo"},
        {"id":"07CB593A-8F8A-4100-B650-BB949775EBA4", "key":"California", "value":"Ontario"},
        {"id":"0884A3CD-2D54-4B4C-9492-4BCF168F04DA", "key":"California", "value":"Salinas"},
        {"id":"0C835A0A-6109-4986-904B-5C6102D86EB4", "key":"California", "value":"San Buenaventura (Ventura)"},
        {"id":"10687A90-F40B-4154-BF46-A1D976C8E800", "key":"California", "value":"Fresno"},
        ...
    ]
}

Neat!

What about if I want to retrieve the list of cities of Alaska and Wisconsin?

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    keys   :[ "Alaska", "Wisconsin" ]
});

and I get:

{"total_rows":285, "offset":4, "rows":[
    {"id":"91B00C11-6648-4E15-83F2-957561ACE157", "key":"Alaska", "value":"Anchorage"},
    {"id":"0A2056EC-4C2E-4285-8CCE-EA0F4C71D353", "key":"Wisconsin", "value":"Milwaukee"},
    {"id":"322DC6B3-3B5E-4310-B674-6957A40A4D03", "key":"Wisconsin", "value":"Green Bay"},
    {"id":"E1371AA5-18DB-48A4-AD90-B212030E6C6F", "key":"Wisconsin", "value":"Madison"}
]}

NOTE: Realize that I have had to change key by keys (plural) and then define the list of values to choose from.

Using reduce

Lets define a reduce function for getting the states of our database.

Getting unique values in CouchDB

Using this same database, if I want to retrieve the states selecting the key of the already defined map function or even if I define a map function as:

function(doc) {
    if (doc.type==="cities") emit(null, doc.state );
}

I get the states as many times, as cities for that state I have in the database (I get states duplicated).

{
    "total_rows":285,
    "offset"    :0,
    "rows"      :[
        {"id":"0254D19B-7360-459E-A9F2-715E36AD1822", "key":null, "value":"California"},
        {"id":"02C0F725-A811-492F-BCC0-8921E04899F4", "key":null, "value":"Indiana"},
        {"id":"04A29C30-CCDC-4D73-B9D4-D8ABBBA9AB46", "key":null, "value":"Texas"},
        {"id":"05C7AD05-0CC6-4C6E-A21B-E4C8344C242A", "key":null, "value":"Colorado"},
        {"id":"07A64E55-2AB1-4AA2-81E8-3D1D54508A26", "key":null, "value":"California"},
        {"id":"07CB593A-8F8A-4100-B650-BB949775EBA4", "key":null, "value":"California"},
        {"id":"07F27430-1A5A-4F12-9116-C720AE84E246", "key":null, "value":"Utah"},
        {"id":"0884A3CD-2D54-4B4C-9492-4BCF168F04DA", "key":null, "value":"California"},
        {"id":"090E0E6C-A4EC-4E84-B071-5C771ADA3DB2", "key":null, "value":"Minnesota"},
        {"id":"094E4363-69EA-443A-964D-29F3B2F04546", "key":null, "value":"Texas"},
        {"id":"0A2056EC-4C2E-4285-8CCE-EA0F4C71D353", "key":null, "value":"Wisconsin"},
        {"id":"0C835A0A-6109-4986-904B-5C6102D86EB4", "key":null, "value":"California"},
        {"id":"0CFD3158-AD42-448B-83A8-BB13C5FF87EF", "key":null, "value":"Washington"},
        {"id":"0D3EE837-0BC1-47CE-A8FA-3FA54400549C", "key":null, "value":"Ohio"},
        {"id":"10687A90-F40B-4154-BF46-A1D976C8E800", "key":null, "value":"California"}
        ...
    ]
}

The way of getting only unique values in CouchDB is defining a reduce function as follow:

function(keys, values) {
    return true;
}

and then ask CouchDB for reducing and grouping the result of map function.

Using map/reduce in jquery.couchdb.js

Choosing to reduce the result is not much different, basically I have to define the reduce function for our view:

function(keys, values) {
    return true;
}

And invoking view in jquery.couchdb is:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    reduce: true,
    group: true
});

And I get:

{"rows":[
    {"key":"Alabama", "value":true},
    {"key":"Alaska", "value":true},
    {"key":"Arizona", "value":true},
    {"key":"Arkansas", "value":true},
    {"key":"California", "value":true},
    ...
]}

NOTE: As an alternative to this reduce function, I might have decided to provide a little more functionality by choosing to return as value the number of cities found for that state. If so, I can simply use a built-in function called _count and my reduce would be:

_count

Yes! just _count, as I said its a built-in function and then I don’t need to provide the body of the function, the name is enough.

I would invoke the view in the same way (with reduce and group set to true) and I get:

{
    "rows":[
        {"key":"Alabama", "value":4},
        {"key":"Alaska", "value":1},
        {"key":"Arizona", "value":10},
        {"key":"Arkansas", "value":1},
        {"key":"California", "value":69},
        ...
    ]
} 

Summarizing

Adding extra attributes to a view is adding extra pairs of keyvalue to the options (second argument) of view method. Here I’ve covered key, keys, reduce and group but you can also use others as startkey and endkey:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    group  :true,
    reduce :true,
    startkey: "Arizona",
    endkey: "Colorado"
});

That retrieves:

{"rows":[
    {"key":"Arizona", "value":true},
    {"key":"Arkansas", "value":true},
    {"key":"California", "value":true},
    {"key":"Colorado", "value":true}
]} 
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s