{% 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.