How to simulating the aggregate functions avg, sum, max, min, and count on PouchDB?

泄露秘密 提交于 2019-12-01 08:50:38

You can use the map/reduce functions of the db.query() method from PouchDB to get the average, sum, largest or any other kind of aggregation of the docs.

I have created a demo JSBin fiddle with a running example. I added the explanation of the functions directly into the code (below) as comments, as I thought it'd be simpler.

var db = new PouchDB('friendsdb');
var docs = [
      {'_id': '1', 'number': 10, 'values': '1, 2, 3', 'loto': 'fooloto'},
      {'_id': '2', 'number': 12, 'values': '4, 7, 9', 'loto': 'barloto'},
      {'_id': '3', 'number': 13, 'values': '9, 4, 5', 'loto': 'fooloto'}
];

db.bulkDocs(docs).then(function(result) {
  querySum();
  queryLargest();
  querySmallest();
  queryAverage();
}).catch(function(err) {
  console.log(err);
});

function querySum() {
  function map(doc) {
    // the function emit(key, value) takes two arguments
    // the key (first) arguments will be sent as an array to the reduce() function as KEYS
    // the value (second) arguments will be sent as an array to the reduce() function as VALUES
    emit(doc._id, doc.number);
  }
  function reduce(keys, values, rereduce) {
    // keys:
    //   here the keys arg will be an array containing everything that was emitted as key in the map function...
    //   ...plus the ID of each doc (that is included automatically by PouchDB/CouchDB).
    //   So each element of the keys array will be an array of [keySentToTheEmitFunction, _idOfTheDoc]
    //
    // values
    //   will be an array of the values emitted as value
    console.info('keys ', JSON.stringify(keys));
    console.info('values ', JSON.stringify(values));
    // check for more info: http://couchdb.readthedocs.io/en/latest/couchapp/views/intro.html


    // So, since we want the sum, we can just sum all items of the values array
    // (there are several ways to sum an array, I'm just using vanilla for to keep it simple)
    var i = 0, totalSum = 0;
    for(; i < values.length; i++){
        totalSum += values[i];
    }
    return totalSum;
  }
  db.query({map: map, reduce: reduce}, function(err, response) {
    console.log('sum is ' + response.rows[0].value);
  });
}

function queryLargest() {
  function map(doc) {
    emit(doc._id, doc.number);
  }
  function reduce(keys, values, rereduce) {
    // everything same as before (see querySum() above)
    // so, this time we want the larger element of the values array

    // http://stackoverflow.com/a/1379560/1850609
    return Math.max.apply(Math, values);
  }
  db.query({map: map, reduce: reduce}, function(err, response) {
    console.log('largest is ' + response.rows[0].value);
  });
}

function querySmallest() {
  function map(doc) {
    emit(doc._id, doc.number);
  }
  function reduce(keys, values, rereduce) {
    // all the same... now the looking for the min
    return Math.min.apply(Math, values);
  }
  db.query({map: map, reduce: reduce}, function(err, response) {
    console.log('smallest is ' + response.rows[0].value);
  });
}

function queryAverage() {
  function map(doc) {
    emit(doc._id, doc.number);
  }
  function reduce(keys, values, rereduce) {
    // now simply calculating the average
    var i = 0, totalSum = 0;
    for(; i < values.length; i++){
        totalSum += values[i];
    }
    return totalSum/values.length;
  }
  db.query({map: map, reduce: reduce}, function(err, response) {
    console.log('average is ' + response.rows[0].value);
  });
}

Note: This is just one way to do it. There are several other possibilities (not emitting IDs as keys, using groups and different reduce functions, using built-in reduce functions, such as _sum, ...), I just thought this was the simpler alternative generally speaking.

The highest and lowest values of the numbers fields are retrievable using the built-in _stats reduce function.

var myMapReduceFun = {
  map: function (doc) {
    emit(doc._id, doc.number);
  },
  reduce: '_stats'
};

db.query(myMapReduceFun, {reduce: true}).then(function (result) {
  // handle result
}).catch(function (err) {
  // handle errors
});

The result looks similar to this:

{"sum":35,"count":3,"min":10,"max":13,"sumsqr":214}

The highest value is in the "max"-field, the lowest in the "min"-field. Now you just have to calculate your desired average, for example the mean average:

var meanAverage = result.sum / result.count;

Other built-in reduce functions in PouchDB are _count and _sum.

The PouchDB documentation says the following about reduce functions:

Tip: if you’re not using a built-in, you’re probably doing it wrong.

I'm a fan of views in PouchDB for problems like this.

https://pouchdb.com/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html

It is possible to create a stored view that allows you to requery the same index multiple times: meaning while the first time through is slow (full scan), later queries will be much faster as the data has already been indexed.

var db = new PouchDB('friendsdb');

var view = {
  '_id':'_design/metrics',
  'views':{
    'metrics':{
      'map':function(doc){
        // collect up all the data we are looking for
        emit(doc._id, doc.number);
      }.toString(),
      'reduce':function(keys, values, rereduce){
        var metrics = {
          sum:0,
          max:Number.MIN_VALUE,
          min:Number.MAX_VALUE,
          count:0
        };
        // aggregate up the values
        for(var i=values.length-1; i>=0; i--){
          var v = values[i];
          metrics.sum += v;
          metrics.max = (metrics.max < v) ? v : metrics.max;
          metrics.min = (metrics.min < v) ? metrics.min : v;
          metrics.count += v.count || 1;
        }
        metrics.avg = metrics.sum/metrics.count;
        return metrics;
      }.toString()
    }
  }
};

// alternately you could use a built in reduce
// if one already exists for the aggregation 
// you are looking for
//view.reduce = '_stats';

// note the addition of the view
var docs = [view
  ,{'_id':'1','number':10,'values':[1,2,3],'loto':'fooloto'}
  ,{'_id':'2','number':12,'values':[4,7,9],'loto':'barloto'}
  ,{'_id':'3','number':13,'values':[9,4,5],'loto':'fooloto'}
];

db.bulkDocs(docs).then(function(result) {
  db.query('metrics',{reduce:true},function(err, response) {
    var m = response.rows[0].value;
    console.log('smallest.: ' + m.min);
    console.log('largest..: ' + m.max);
    console.log('average..: ' + m.avg);
    console.log('count....: ' + m.count);
    console.log('Total ...: ' + m.sum);
  });
}).catch(function(err) {
  console.log(err);
});

Note the addition of the view to the data you load into your database, as well as the fact that the map and reduce are requried to be converted to strings (the .toString() at the end of the function)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!