{% include anchor.html edit="true" title="Query the database" hash="query_database" %} {% highlight js %} db.query(fun, [options], [callback]) {% endhighlight %} Invoke a map/reduce function, which allows you to perform more complex queries on PouchDB than what you get with [allDocs()](#batch_fetch). The [CouchDB documentation for map/reduce](http://docs.couchdb.org/en/latest/couchapp/views/intro.html) applies to PouchDB. Since views perform a full scan of all documents, this method may be slow, unless you first save your view in a design document. Read the [query guide](/guides/queries.html) for a good tutorial. {% include alert/start.html variant="info"%} {% markdown %} **Heads up:** eventually the `query()` API will be deprecated in favor of the much simpler [pouchdb-find plugin](https://github.com/nolanlawson/pouchdb-find). You will still be able to use `query()`, but it will be distributed as [a separate plugin](https://github.com/pouchdb/mapreduce). {% endmarkdown %} {% include alert/end.html%} ### Options All options default to `false` unless otherwise specified. * `fun`: Map/reduce function, which can be one of the following: * A full CouchDB-style map/reduce view: `{map : ..., reduce: ...}`. * A map function by itself (no reduce). * The name of a view in an existing design document (e.g. `'mydesigndoc/myview'`, or `'myview'` as a shorthand for `'myview/myview'`). * `options.reduce`: Reduce function, or the string name of a built-in function: `'_sum'`, `'_count'`, or `'_stats'`. Defaults to `false` (no reduce). * Tip: if you're not using a built-in, [you're probably doing it wrong](http://youtu.be/BKQ9kXKoHS8?t=865s). * PouchDB will always call your reduce function with rereduce == false. As for CouchDB, refer to the [CouchDB documentation](http://docs.couchdb.org/en/1.6.1/couchapp/views/intro.html). * `options.include_docs`: Include the document in each row in the `doc` field. - `options.conflicts`: Include conflicts in the `_conflicts` field of a doc. - `options.attachments`: Include attachment data. - `options.binary`: Return attachment data as Blobs/Buffers, instead of as base64-encoded strings. * `options.startkey` & `options.endkey`: Get rows with keys in a certain range (inclusive/inclusive). * `options.inclusive_end`: Include rows having a key equal to the given `options.endkey`. Default: `true`. * `options.limit`: Maximum number of rows to return. * `options.skip`: Number of rows to skip before returning (warning: poor performance on IndexedDB/LevelDB!). * `options.descending`: Reverse the order of the output rows. * `options.key`: Only return rows matching this key. * `options.keys`: Array of keys to fetch in a single shot. - Neither `startkey` nor `endkey` can be specified with this option. - The rows are returned in the same order as the supplied `keys` array. - The row for a deleted document will have the revision ID of the deletion, and an extra key `"deleted":true` in the `value` property. - The row for a nonexistent document will just contain an `"error"` property with the value `"not_found"`. * `options.group`: True if you want the reduce function to group results by keys, rather than returning a single result. Defaults to `false`. * `options.group_level`: Number of elements in a key to group by, assuming the keys are arrays. Defaults to the full length of the array. * `options.stale`: One of `'ok'` or `'update_after'`. Only applies to saved views. Can be one of: * unspecified (default): Returns the latest results, waiting for the view to build if necessary. * `'ok'`: Returns results immediately, even if they're out-of-date. * `'update_after'`: Returns results immediately, but kicks off a build afterwards. For details, see the [CouchDB query options documentation](http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options). #### Example Usage: {% include code/start.html id="query1" type="callback" %} {% highlight js %} // create a design doc var ddoc = { _id: '_design/index', views: { index: { map: function mapFun(doc) { if (doc.title) { emit(doc.title); } }.toString() } } } // save the design doc db.put(ddoc, function (err) { if (err && err.name !== 'conflict') { return console.log(err); } // ignore if doc already exists // find docs where title === 'Lisa Says' db.query('index', { key: 'Lisa Says', include_docs: true }, function (err, result) { if (err) { return console.log(err); } // handle result }); }); {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query1" type="async" %} {% highlight js %} // create a design doc var ddoc = { _id: '_design/index', views: { index: { map: function mapFun(doc) { if (doc.title) { emit(doc.title); } }.toString() } } } // save the design doc try { try { await db.put(ddoc); } catch (err) { if (err.name !== 'conflict') { throw err; } // ignore if doc already exists } // find docs where title === 'Lisa Says' var result = await db.query('index', { key: 'Lisa Says', include_docs: true }); } catch (err) { console.log(err); } {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query1" type="promise" %} {% highlight js %} // create a design doc var ddoc = { _id: '_design/index', views: { index: { map: function mapFun(doc) { if (doc.title) { emit(doc.title); } }.toString() } } } // save the design doc db.put(ddoc).catch(function (err) { if (err.name !== 'conflict') { throw err; } // ignore if doc already exists }).then(function () { // find docs where title === 'Lisa Says' return db.query('index', { key: 'Lisa Says', include_docs: true }); }).then(function (result) { // handle result }).catch(function (err) { console.log(err); }); {% endhighlight %} {% include code/end.html %} #### Example Response: {% highlight js %} { "offset" : 0, "rows": [{ "id": "doc3", "key": "Lisa Says", "value": null, "doc": { "_id": "doc3", "_rev": "1-z", "title": "Lisa Says" } }], "total_rows" : 4 } {% endhighlight %} In the result,`total_rows` is the total number of possible results in the view. The response is very similar to that of `allDocs()`. **Note:** you can also pass in the map function instead of saving a design doc first, but this is slow because it has to do a full database scan. The following examples will use this pattern for simplicity's sake, but you should normally avoid it. #### Complex keys You can also use [complex keys](https://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Complex_Keys) for fancy ordering: {% include code/start.html id="query2" type="callback" %} {% highlight js %} function map(doc) { // sort by last name, first name, and age emit([doc.lastName, doc.firstName, doc.age]); } db.query(map, function (err, response) { if (err) { return console.log(err); } // handle result }); {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query2" type="async" %} {% highlight js %} function map(doc) { // sort by last name, first name, and age emit([doc.lastName, doc.firstName, doc.age]); } try { var result = await db.query(map); } catch (err) { console.log(err); } {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query2" type="promise" %} {% highlight js %} function map(doc) { // sort by last name, first name, and age emit([doc.lastName, doc.firstName, doc.age]); } db.query(map).then(function (result) { // handle result }).catch(function (err) { console.log(err); }); {% endhighlight %} {% include code/end.html %} #### Example Response: {% highlight js %} { "offset": 0, "rows": [{ "id" : "bowie", "key" : ["Bowie", "David", 67] }, { "id" : "dylan", "key" : ["Dylan", "Bob", 72] }, { "id" : "younger_dylan", "key" : ["Dylan", "Jakob", 44] }, { "id" : "hank_the_third", "key" : ["Williams", "Hank", 41] }, { "id" : "hank", "key" : ["Williams", "Hank", 91] }], "total_rows": 5 } {% endhighlight %} **Tips:** * The sort order is `[nulls, booleans, numbers, strings, arrays, objects]`, so `{startkey: ['Williams'], endkey: ['Williams', {}]}` would return all people with the last name `'Williams'` because objects are higher than strings. Something like `'zzzzz'` or `'\uffff'` would also work. * `group_level` can be very helpful when working with complex keys. In the example above, you can use `{group_level: 1}` to group by last name, or `{group_level: 2}` to group by last and first name. (Be sure to set `{reduce: true, group: true}` as well.) #### Linked documents PouchDB fully supports [linked documents](https://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents). Use them to join two types of documents together, by simply adding an `_id` to the emitted value: {% include code/start.html id="query3" type="callback" %} {% highlight js %} function map(doc) { // join artist data to albums if (doc.type === 'album') { emit(doc.name, {_id : doc.artistId, albumYear : doc.year}); } } db.query(map, {include_docs : true}, function (err, response) { if (err) { return console.log(err); } // handle result }); {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query3" type="async" %} {% highlight js %} function map(doc) { // join artist data to albums if (doc.type === 'album') { emit(doc.name, {_id : doc.artistId, albumYear : doc.year}); } } try { var result = await db.query(map, {include_docs : true}); } catch (err) { console.log(err); } {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query3" type="promise" %} {% highlight js %} function map(doc) { // join artist data to albums if (doc.type === 'album') { emit(doc.name, {_id : doc.artistId, albumYear : doc.year}); } } db.query(map, {include_docs : true}).then(function (result) { // handle result }).catch(function (err) { console.log(err); }); {% endhighlight %} {% include code/end.html %} #### Example response: {% highlight js %} { "offset": 0, "rows": [ { "doc": { "_id": "bowie", "_rev": "1-fdb234b78904a5c8293f2acf4be70d44", "age": 67, "firstName": "David", "lastName": "Bowie" }, "id": "album_hunkeydory", "key": "Hunky Dory", "value": { "_id": "bowie", "albumYear": 1971 } }, { "doc": { "_id": "bowie", "_rev": "1-fdb234b78904a5c8293f2acf4be70d44", "age": 67, "firstName": "David", "lastName": "Bowie" }, "id": "album_low", "key": "Low", "value": { "_id": "bowie", "albumYear": 1977 } }, { "doc": { "_id": "bowie", "_rev": "1-fdb234b78904a5c8293f2acf4be70d44", "age": 67, "firstName": "David", "lastName": "Bowie" }, "id": "album_spaceoddity", "key": "Space Oddity", "value": { "_id": "bowie", "albumYear": 1969 } } ], "total_rows": 3 } {% endhighlight %} #### Closures If you pass a function to `db.query` and give it the `emit` function as the second argument, then you can use a closure. (Since PouchDB has to use `eval()` to bind `emit`.) {% include code/start.html id="query4" type="callback" %} {% highlight js %} // BAD! will throw error var myId = 'foo'; db.query(function(doc) { if (doc._id === myId) { emit(doc); } }, function(err, results) { // you'll get an error here }); // will be fine var myId = 'foo'; db.query(function(doc, emit) { if (doc._id === myId) { emit(doc); } }, function(err, results) { if (err) { return console.log(err); } // handle result }); {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query4" type="async" %} {% highlight js %} // BAD! will throw error var myId = 'foo'; try { var result = await db.query(function(doc) { if (doc._id === myId) { emit(doc); } }); } catch (err) { // you'll get an error here } // will be fine var myId = 'foo'; try { var result = await db.query(function(doc, emit) { if (doc._id === myId) { emit(doc); } }); } catch (err) { console.log(err); } {% endhighlight %} {% include code/end.html %} {% include code/start.html id="query4" type="promise" %} {% highlight js %} // BAD! will throw error var myId = 'foo'; db.query(function(doc) { if (doc._id === myId) { emit(doc); } }).catch(function (err) { // you'll get an error here } // will be fine var myId = 'foo'; db.query(function(doc, emit) { if (doc._id === myId) { emit(doc); } }).then(function (result) { // handle result }).catch(function (err) { console.log(err); }); {% endhighlight %} {% include code/end.html %} Note that closures are only supported by local databases with temporary views. So if you are using closures, then you must use the slower method that requires a full database scan.