在mongo中收集地图后执行聚合

时间:2014-04-18 01:43:27

标签: mongodb mongodb-query aggregation-framework

我希望在将mongo db传递给聚合函数之前操作一些记录。特别是,在对这些属性执行求和之前,我需要总结集合的一些属性。

最初不能在聚合查询中完成属性的求和,因为属性名称在原始集合中有所不同。例如,我从以下内容开始:

{ timestamp: 1346774400000, foo3: 12, foo45: 13, foo9: 2 }, 
{ timestamp: 1346796000000, foo7: 33, foo2: 5 }

我需要修改每个文档以总结以" foo"开头的每个属性的值,然后对集合中每个文档的所有这些值求和。

我写了一个地图操作,这会产生类似的东西:

{ timestamp: 1346774400000, foo_total: 27 }, 
{ timestamp: 1346796000000, foo_total: 38 } 

...但我无法对db.collection.map()的输出执行聚合函数。

有没有办法实现这个或者更好的方法呢?我无法更改文档的现有结构,我想避免执行map reduce操作,我不想将此操作卸载到代码中。

1 个答案:

答案 0 :(得分:1)

如上所述,文档中不同键值的问题在于聚合不能专门处理这些问题,至少在不知道所有可能的值并编写非常长的语句的情况下。

当然,您现在的方法是在检索它们之后处理收集结果,并且实际上并不会导致收集本身,因此无法将其传递给聚合。

所以最好的方法是将整个事情传递给mapReduce,逻辑非常简单。首先是一个映射器:

var mapper = function () {

  var patt = /^([a-z|A-Z]+)/;

  var total = {};

  for ( n in this ) {

      if ( (n == "timestamp") || n == "_id" )
        continue;

      var match = patt.exec(n)[0];
      if (!total.hasOwnProperty(match))
        total[match] = 0;

      total[match] += this[n];

  }

  emit( null, total );

};

所以很简单,这只是“询问”字段名称,同时排除任何你不知道的字段名称。在这种情况下,使用正则表达式匹配字段名称中的第一个“alpha”字符。我允许字段可能是“foo16”,bar32“,”baz12“的可能性,并且这些都不会对操作产生影响。无论如何,一些方法可以剥离你想要的字段部分。

这些值在每个文档内部添加并发送到reducer,因为只有一个“key”,即null

所以在reducer:

var reducer = function (key,values) {

  var reduced = {};

  values.forEach(function(value) {
    for ( var n in value ) {
      if ( !reduced.hasOwnProperty(n) )
        reduced[n] = 0;

      reduced[n] += value[n];
    }
  });

  return reduced;

};

这类似地循环发出的每个文档,并对找到的每个“字段”的结果求和,以产生结果:

{
    "results" : [
            {
                    "_id" : null,
                    "value" : {
                            "foo" : 65
                    }
            }
    ],
    "timeMillis" : 7,
    "counts" : {
            "input" : 2,
            "emit" : 2,
            "reduce" : 1,
            "output" : 1
    },
    "ok" : 1,
}

仅基于您拥有的示例文档。