Back to Pouchdb

Query Database

docs/_includes/api/query_database.html

9.0.012.8 KB
Original Source

{% include anchor.html edit="true" title="Map/reduce queries" 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(), changes(), or find(). The CouchDB documentation for map/reduce 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 for a good tutorial. {% include alert/start.html variant="warning"%} {% markdown %} **Warning: advanced API.** The map/reduce API is designed for cases that are too complex for Mango queries, which are described in createIndex(), find(), listIndexes(), and deleteIndex(). Under the hood, Mango indexes are the same as map/reduce indexes. The Mango API is just a simplified user-facing API on top of map/reduce. {% 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: Defaults to true when a reduce function is defined, or false otherwise. Valid values: * true - calls the defined reduce function, or the map function if no reduce is defined. * false - calls the defined map function. * A reduce function. * The string name of a built-in function: '_sum', '_count', or '_stats'. * Tip: if you're not using a built-in, you're probably doing it wrong. * PouchDB will always call your reduce function with rereduce == false. As for CouchDB, refer to the CouchDB documentation. * 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. * options.update_seq: Include an update_seq value indicating which sequence id of the underlying database the view reflects. For details, see the CouchDB query options documentation. #### Example Usage: {% include code/start.html id="query1" type="callback" %} {% highlight "js" %} // create a design doc const 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 const 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' const 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 const 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(). The result may also have update_seq if you set update_seq to true **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 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 { const 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 '\ufff0' 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. 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 { const 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 const myId = 'foo'; db.query(function(doc) { if (doc._id === myId) { emit(doc); } }, function(err, results) { // you'll get an error here }); // will be fine const 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 const myId = 'foo'; try { const result = await db.query(function(doc) { if (doc._id === myId) { emit(doc); } }); } catch (err) { // you'll get an error here } // will be fine const myId = 'foo'; try { const 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 const myId = 'foo'; db.query(function(doc) { if (doc._id === myId) { emit(doc); } }).catch(function (err) { // you'll get an error here } // will be fine const 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.