Skip to content

Commit 325b248

Browse files
committed
Update: 23-05-2025
1 parent bf27c2c commit 325b248

File tree

1 file changed

+167
-19
lines changed

1 file changed

+167
-19
lines changed

query/creating_views.md

Lines changed: 167 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -223,37 +223,80 @@ Whenever possible,
223223
you must use one of these functions instead of writing your own.
224224

225225
To use one of the built-in functions,
226-
put the name into the `reduce` field of the view object in your design document.
226+
put the reducer's name into the `reduce` field of the view object in your design document.
227+
228+
#### Count reducer
229+
{: #built-in-reduce-functions-count-reducer}
230+
231+
The `_count` reducer counts a MapReduce view's rows and optionally groups the counts by distinct keys.
227232

228233
```json
229234
{
230235
"views": {
231-
"sumPrices": {
232-
"map": "function(user) { if(user.email_verified === true) { emit(user.name, user.email); } }",
236+
"teamCount": {
237+
"map": "function(doc) { if (doc.email_verified === true) { emit(doc.team, doc.name); } }",
233238
"reduce": "_count"
234239
}
235240
}
236241
}
237242
```
238243
{: codeblock}
239244

240-
The previous MapReduce view creates an index that is keyed on the username and whose counts all active email. As the reducer is `_count`, the view outputs the total email count for the selection of data queried. It is suitable for counting the registered users.
245+
The previous MapReduce view creates an index that is keyed on the `team` the user belongs to, but only includes those with a verified email address. As the reducer is `_count`, the view outputs the number of rows in the view e.g the number of verified users in the database.
246+
247+
```json
248+
{"rows":[
249+
{"key":null,"value":10010}
250+
]}
251+
```
252+
{: codeblock}
253+
254+
By adding `?group=true`, the counts are grouped by distinct keys so the database outputs counts by team membership:
255+
256+
```json
257+
{"rows":[
258+
{"key":"blue","value":1409},
259+
{"key":"green","value":1439},
260+
{"key":"indigo","value":1425},
261+
{"key":"orange","value":1432},
262+
{"key":"red","value":1414},
263+
{"key":"violet","value":1443},
264+
{"key":"yellow","value":1448}
265+
]}
266+
```
267+
{: codeblock}
268+
269+
Switching the reducer off allows the same view to be used for selection of a single team's members `?key="orange"&reduce=false&limit=5`:
270+
271+
```json
272+
{"total_rows":10010,"offset":4273,"rows":[
273+
{"id":"783173e102613c78d02a2b3304001642","key":"orange","value":"Bethel Lusk"},
274+
{"id":"783173e102613c78d02a2b3304001b40","key":"orange","value":"Ethyl Dionne"},
275+
{"id":"783173e102613c78d02a2b3304009c66","key":"orange","value":"Fredda Hendrix"},
276+
{"id":"783173e102613c78d02a2b330401800d","key":"orange","value":"Bibi Page"},
277+
{"id":"783173e102613c78d02a2b3304018ae7","key":"orange","value":"Marylou Lavender-Milton"}
278+
]}
279+
```
280+
{: codeblock}
281+
282+
#### Sum reducer
283+
{: #built-in-reduce-functions-sum-reducer}
241284

242-
The numeric reducers `_stats`/`_sum` act upon the value (the emit function's second parameter) which can be a number, array, or object. Consider the following MapReduce definition on the `products` partitioned database:
285+
The `_sum` reducer totalizes a MapReduce view's emitted numeric values. The view's value can be a number, an array of numbers, or an object containing numeric values. Consider the following MapReduce definition on a database of products:
243286

244287
```json
245288
{
246289
"views": {
247-
"statsReadingsObject": {
248-
"map": "function(product) { emit(product.type, { price: product.price, tax: product.tax }); }",
290+
"productPrices": {
291+
"map": "function(doc) { emit(doc.type, { price: doc.price, tax: doc.tax }); }",
249292
"reduce": "_sum"
250293
}
251294
}
252295
}
253296
```
254297
{: codeblock}
255298

256-
The view is keyed on the type of the product, and the value is an object that contains two values: price and tax. The `_sum` reduce calculates totals for each attribute of the object that it finds:
299+
The view is keyed on the type of the product, and the value is an object that contains two values: `price` and `tax`. The `_sum` reducer calculates totals for both the `price` and `tax` values across the view:
257300

258301
```json
259302
{"rows":[
@@ -262,22 +305,25 @@ The view is keyed on the type of the product, and the value is an object that co
262305
```
263306
{: codeblock}
264307

265-
Or add `?group=true` when querying the view. The output is grouped and summed by a unique key, in this case, `type`:
308+
By adding `?group=true` when querying the view, the output is grouped and summed by a unique key, in this case, the product type:
266309

267310
```json
268311
{"rows":[
269-
{"key":"portable","value":{"price":14.99,"tax":1.14}},
270-
{"key":"product","value":{"price":129.98,"tax":6.18}}
312+
{"key":"kitchen","value":{"price":14.99,"tax":1.14}},
313+
{"key":"garden","value":{"price":129.98,"tax":6.18}}
271314
]}
272315
```
273316
{: codeblock}
274317

275-
The numeric reducers also calculate multiple reductions when the value of an index is an array of numbers:
318+
#### Stats reducer
319+
{: #built-in-reduce-functions-stats-reducer}
320+
321+
Like the `_sum` reducer, the `_stats` reducer works on numbers, objects with numeric values or arrays of numbers, returning counts, sums, minimum & maximum values and a sum of the square of the values, which is useful for variance or standard deviation calculations:
276322

277323
```json
278324
{
279325
"views": {
280-
"statsReadingsArray": {
326+
"salesByDate": {
281327
"map": "function(doc) { emit(doc.date, [doc.price, doc.tax]); }",
282328
"reduce": "_stats"
283329
}
@@ -290,49 +336,151 @@ The previous definition calculates statistics on the numerical values that it fi
290336

291337
```json
292338
{"rows":[
293-
{"key":"portable","value":[
339+
{"key":"2025-01-01","value":[
294340
{"sum":14.99,"count":1,"min":14.99,"max":14.99,"sumsqr":224.7001},
295341
{"sum":1.14,"count":1,"min":1.14,"max":1.14,"sumsqr":1.2995}
296342
]},
297-
{"key":"product","value":[
343+
{"key":"2025-01-02","value":[
298344
{"sum":129.98,"count":2,"min":29.99,"max":99.99,"sumsqr":10897.4002},
299345
{"sum":6.18,"count":2,"min":1.62,"max":4.56,"sumsqr":23.418}
300346
]}
301347
]}
302348
```
303349
{: codeblock}
304350

305-
The `_count` reducer simply counts the number of `key-value` pairs that are emitted into the index.
351+
#### The approximate count distinct reducer
352+
{: #built-in-reduce-functions-approx-count-distinct-reducer}
353+
354+
Unlike the numeric reducers `_sum` and `_stats` which act upon the index's value, the `_approx_count_distinct` reducer uses the view's _key_. It estimates the number of distinct keys found in the MapReduce view using an algorithm which uses far less memory than an exact count distinct algorithm would consume:
355+
356+
```json
357+
{
358+
"views": {
359+
"estimateIpCount": {
360+
"map": "function (doc) {\n emit(doc.ip, 1);\n}",
361+
"reduce": "_approx_count_distinct"
362+
}
363+
}
364+
}
365+
```
366+
{: codeblock}
367+
368+
The previous view definition aims to estimate the number of distinct IP addresses in a database of server logs. The document's `ip` is emitted as the index's _key_ so that the `_approx_count_distinct` reducer can estimate the count of distinct keys:
306369

307370
```json
308371
{"rows":[
309-
{"key":"product","value":3}
372+
{"key":null,"value":100528}
310373
]}
311374
```
312375
{: codeblock}
313376

314-
The `_approx_count_distinct_reducer` acts upon the _key_ of the index, as opposed to the numeric reducers that act upon the index's _value_.
377+
#### The top/bottom reducers
378+
{: #built-in-reduce-functions-top-bottom-reducer}
379+
380+
The `_top_x` and `_bottom_x` reducers (where `x` is a number between 1 and 100) return an array of the top x or bottom x _values_ in a view grouping, respectively. For example, in a gaming application, a view can be created keyed on the user id and whose value is the score that the user achieved. This view can be used to create a scoreboard of the best or worst scores:
381+
382+
```json
383+
{
384+
"views": {
385+
"bestScores": {
386+
"map": "function(doc) { emit(doc.user_id, doc.score); }",
387+
"reduce": "_top_3"
388+
}
389+
}
390+
}
391+
```
392+
{: codeblock}
393+
394+
If we query the view without any parameters, the top three scores in the entire view are returned:
315395

316396
```json
317397
{"rows":[
318-
{"key":null,"value":2}
398+
{"key":null,"value":[99,98,97]}
319399
]}
320400
```
321401
{: codeblock}
322402

403+
With grouping (`?group=true`), each distinct user's top three scores are returned:
404+
405+
```json
406+
{"rows":[
407+
{"key":"user082","value":[99,98,97]},
408+
{"key":"user291","value":[85,72,42]},
409+
{"key":"user452","value":[55,51,30]}
410+
]}
411+
```
412+
{: codeblock}
413+
414+
#### The first/last reducers
415+
{: #built-in-reduce-functions-first-last-reducer}
416+
417+
The `_first`/`_last` reducers return the _value_ of the first or last _key_ in a view grouping, respectively. If we have an IoT application storing readings from many devices periodically, we can create a view keyed on the device id & the time the reading was taken. The view's _value_ is the entire document:
418+
419+
```json
420+
{
421+
"views": {
422+
"latestReading": {
423+
"map": "function(doc) { emit([doc.deviceid, doc.timestamp], doc); }",
424+
"reduce": "_last"
425+
}
426+
}
427+
}
428+
```
429+
{: codeblock}
430+
431+
This view produces keys and values of this form, with the view sorted by `deviceid` and `timestamp`. The rows considered the "first" and "last" readings for each device are highlighted:
432+
433+
| key | value | First reading (group_level=1) | Last reading (group_level=1) |
434+
|------------------------------------|--------------------------------------------------------------------------------------------|-------------------------------|------------------------------|
435+
| ["A00","2025-01-01T10:00:00.000Z"] | {"_id": "A00:5000","reading": 65,"timestamp":2025-01-01T10:00:00.000Z","deviceid":"A00"} | x | |
436+
| ["A00","2025-01-01T10:01:00.000Z"] | {"_id": "A00:5001","reading": 64,"timestamp":2025-01-01T10:01:00.000Z","deviceid":"A00"} | | |
437+
| ["A00","2025-01-01T10:02:00.000Z"] | {"_id": "A00:5002","reading": 59,"timestamp":2025-01-01T10:02:00.000Z","deviceid":"A00"} | | x |
438+
| ["A01","2025-01-01T10:00:00.000Z"] | {"_id": "A01:8000","reading": 12,"timestamp":2025-01-01T10:00:00.000Z","deviceid":"A01"} | x | |
439+
| ["A01","2025-01-01T10:01:00.000Z"] | {"_id": "A01:8001","reading": 15,"timestamp":2025-01-01T10:01:00.000Z","deviceid":"A01"} | | |
440+
| ["A01","2025-01-01T10:02:00.000Z"] | {"_id": "A01:8002","reading": 19,"timestamp":2025-01-01T10:02:00.000Z","deviceid":"A01"} | | x |
441+
| ["A02","2025-01-01T10:00:00.000Z"] | {"_id": "A02:4000","reading": 55,"timestamp":2025-01-01T10:00:00.000Z","deviceid":"A02"} | x | |
442+
| ["A02","2025-01-01T10:01:00.000Z"] | {"_id": "A02:4001","reading": 54,"timestamp":2025-01-01T10:01:00.000Z","deviceid":"A02"} | | |
443+
| ["A02","2025-01-01T10:02:00.000Z"] | {"_id": "A01:4002","reading": 56,"timestamp":2025-01-01T10:02:00.000Z","deviceid":"A02"} | | x |
444+
445+
Querying the view with `group_level=1`, using the `_last` reducer, will return the newest reading for every device_id in the database:
446+
447+
```json
448+
{"rows":[
449+
{"key":["A00"],"value":{"_id":"93117567370d41d091b8dd160a3adf3f","_rev":"1-bc05e93e592d5a5a18e240240b581a55","deviceid":"A00","reading":13.8986,"timestamp":"2025-03-26T04:44:08.917Z","status":"red"}},
450+
{"key":["A01"],"value":{"_id":"c9f53ac9e4a8444487ed0eaa11dc1c78","_rev":"1-1fcf121c73db03e49bac4c1981518b19","deviceid":"A01","reading":59.8453,"timestamp":"2025-04-01T01:52:34.254Z","status":"green"}},
451+
{"key":["A02"],"value":{"_id":"577b7108a8a1458f9a17194ed1da398a","_rev":"1-71d345a773ab31f35ceb998f3c107c41","deviceid":"A02","reading":2.6208,"timestamp":"2025-03-31T00:22:17.175Z","status":"green"}},
452+
{"key":["A03"],"value":{"_id":"150839b1d363427496a4f4e2917b8b1d","_rev":"1-860a2ed5f1f48aa642495dfb21dff3ce","deviceid":"A03","reading":55.8677,"timestamp":"2025-03-22T10:15:57.890Z","status":"red"}},
453+
{"key":["A04"],"value":{"_id":"d3317bdc1f7b4466ae6bf7a30ca9e328","_rev":"1-a58762532f6980ca5b06a8d01d113814","deviceid":"A04","reading":44.1822,"timestamp":"2025-03-23T10:56:10.639Z","status":"green"}},
454+
{"key":["A05"],"value":{"_id":"946754d3762f44e297ca20d13bbceb5e","_rev":"1-41fa2bb3ef8782c90b28ee44628abeab","deviceid":"A05","reading":13.2874,"timestamp":"2025-03-27T13:59:04.723Z","status":"blue"}},
455+
{"key":["A06"],"value":{"_id":"bbcd8a5c0ae948baae713a9fcb5262d5","_rev":"1-66883211ee20a0772374672aa175dc50","deviceid":"A06","reading":7.9525,"timestamp":"2025-04-01T15:13:04.305Z","status":"blue"}},
456+
{"key":["A07"],"value":{"_id":"a600caccdb82400698b158ecebfaa6f2","_rev":"1-68975a8d7133a13c6cc8ae34d28ea1c6","deviceid":"A07","reading":89.4818,"timestamp":"2025-03-07T02:12:59.934Z","status":"blue"}},
457+
{"key":["A08"],"value":{"_id":"be20bae911db4da685f891fd01e07d3a","_rev":"1-d69b559627cf01e80fe85ea2f54d2f4b","deviceid":"A08","reading":97.6739,"timestamp":"2025-03-29T13:46:31.689Z","status":"green"}},
458+
{"key":["A09"],"value":{"_id":"96526e80f2ff48e89e9e42aa47abae24","_rev":"1-85e56901f7113d7d6ff8ba573c535eb3","deviceid":"A09","reading":26.1597,"timestamp":"2025-03-18T03:22:19.848Z","status":"blue"}}
459+
]}
460+
```
461+
{: codeblock}
462+
463+
#### The built-in reducers summary
464+
{: #built-in-reduce-functions-summary}
465+
323466
| Function | Description |
324467
|---------|------------|
325468
| `_count` | Produces the row count for a specific key. The values can be any valid JSON. |
326469
| `_stats` | Produces a JSON structure that contains the sum, the count, the min, the max, and the sum-squared values. All values must be numeric. |
327470
| `_sum` | Produces the sum of all values for a key. The values must be numeric. |
328471
| `_approx_count_distinct` | Approximates the number of distinct keys in a view index by using a variant of the [HyperLogLog](https://en.wikipedia.org/wiki/HyperLogLog){: external} algorithm. |
472+
| `_top_x`/`_bottom_x` | Returns an array of the top x or bottom x _values_ in the view grouping as an array, where `x` is a number between 1 and 100. |
473+
| `_first`/`_last` | Returns the _values_ of the lowest or highest sorting _key_, respectively, for each view group. |
329474
{: caption="Built-in reduce functions" caption-side="top"}
330475

331476
## Custom reduce functions
332477
{: #custom-reduce-functions}
333478

334479
Most customers find that built-in reducers are sufficient to perform aggregations on the view `key-value` pairs emitted from their Map functions. However, for unusual use-cases, a JavaScript reduce function can be supplied instead of the name of one of the built-in reducers.
335480

481+
Custom reduce functions are much slower and more difficult to maintain than built-in reducers, so check if a use-case can be satisfied with a built-in reducer before writing a custom one.
482+
{: tip}
483+
336484
Reduce functions are passed three arguments in the following order:
337485

338486
- `keys`

0 commit comments

Comments
 (0)