{% 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()](#batch_fetch), [changes()](#changes), or [find()](#query_index). The [CouchDB documentation for map/reduce](https://docs.couchdb.org/en/stable/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="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()](#create_index), [find()](#query_index), [listIndexes()](#list_indexes), and [deleteIndex()](#delete_index).

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](http://youtu.be/BKQ9kXKoHS8?t=865s).
    * PouchDB will always call your reduce function with rereduce == false. As for CouchDB, refer to the [CouchDB documentation](https://docs.couchdb.org/en/stable/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` &amp; `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](https://docs.couchdb.org/en/stable/api/ddoc/views.html#get--db-_design-ddoc-_view-view).

#### 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](https://docs.couchdb.org/en/stable/ddocs/views/collation.html#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](https://docs.couchdb.org/en/stable/ddocs/views/joins.html?highlight=linked%20documents#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.
