【问题标题】:Group By aggregations on MongoDbMongoDb 上的 Group By 聚合
【发布时间】:2016-04-29 03:14:06
【问题描述】:

我使用 mongodb 2.6。我以这种形式存储了我的数据:

{ 
    "_id" : "my-sensor-1", 
    "points": [ 
        { "timeStamp" : NumberLong("1453468362174"), "value" : 41 }, 
        { "timeStamp" : NumberLong("1453468483297"), "value" : 66 }, 
        { "timeStamp" : NumberLong("1453468485568"), "value" : 49 },
         ...    
    ] 
}

为了聚合文档,我进行如下查询:

db.pointsTest.aggregate([
    { $match: { $and: [ {"points.timeStamp" : { $gt : 1453433925163}}, 
                        {"_id":"my-sensor-10"} ] } },
    {"$unwind":"$points"},
    {$group: {_id: "my-sensor-1","average":{$avg : "$points.value"}}}
])

{ "_id" : "my-sensor-1", "average" : 52 }

结果

我已将时间戳存储为毫秒,因此每次我想聚合特定时间间隔时,我都必须更改 timeStamp 值的范围。

我如何在一个时间段上进行聚合并按间隔对结果进行分组(即从 now() -1day GROUP by 1h 开始的平均值聚合)?

编辑

我想做这样的事情:

db.pointsTest.aggregate([
    { $match: { $and: [ {"points.timeStamp" : { $gt : 1453433925163, $lt : 1453555555555}}, {"_id":"my-sensor-10"} ] } }, {"$unwind":"$points"}, {$group: {_id: "my-sensor-1","average":{$avg : "$points.value"}, ???}}
])

结果将是按 1 小时分组的该时间间隔的平均值。 假设我想汇总 12 月 31 日每小时的平均值:

时间间隔 31/12/2015 20:00:00 的时间戳,平均值:xyz

时间间隔 31/12/2015 21:00:00 的时间戳,平均值:xyz

此时,为了实现这一点,我必须将时间间隔拆分为 1 小时间隔,并向数据库发出多个请求。

I.E 使用 InfluxDB 做同样的事情我这样做:

"SELECT MEAN(value) From myMeasures where key='my-sensor-1' and time > now() - 1d GROUP BY time(1h)"

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:

    您需要在 Mongo 查询中进行一些数学计算,以根据不同的区间对数据进行分组。

    $subtract 和 $mod 的组合将帮助您按特定间隔对数据进行分组。

    查询将如下所示:

    db.sensor.aggregate({
        $match: {
        $and: [{
            "points.timeStamp": {
                $gt: 1453433925163,
                $lt: 1453555555555
            }
        }, {
            "_id": "my-sensor-1"
        }]
        }
    }, {
        $unwind: "$points"
    }, {
        "$group": {
        "_id": {
            "$subtract": ["$points.timeStamp", {
                "$mod": ["$points.timeStamp", 1000 * 60]
            }]
        },
        "average": {
            "$avg": "$points.value"
        }
        }
    })
    

    希望对你有所帮助。

    【讨论】:

    • 真的很有用,但我认为它缺少一些东西(也许我没有在我的问题中指定它)。为了迭代列表中的项目,我必须添加另一个 $match,因为当条件为真时,它会返回整个项目。所以查询是这样的: db.pointsTest.aggregate({ $match: { $and: [{ "points.timeStamp": { $gte: 1453797806927 } }, { "_id": "my-sensor-1" }] } }, { $unwind: "$points" }, {$match :{ "points.timeStamp":{$gte: 1453797806927 }}} ,{ "$group": { "_id": { "$subtract": ["$points.timeStamp", { "$mod": ["$points.timeStamp", 1000 * 60]}]},"average":{"$avg":"$points.value"}}})
    【解决方案2】:

    要获得间隔间隔的结果,您可以使用 javascript 函数来支持您的查询,因为 mongodb shell 支持它们:

    要使用您提供的示例执行该示例,您希望 $match 来自 now()-1day 的值,您可以在聚合之前执行此操作:

    var now = new Date();
    var yesterday = new Date();
    // using getHours and setHours, since the Date object doesnt have method for getDays and setDays
    yesterday.setHours(now.getHours() - 24);
    

    yesterday.getTime() 将生成以 milis 为单位的日期,您可以在 $match 阶段的聚合中使用它

    db.pointsTest.aggregate([
        { $match: { $and: [ {"points.timeStamp" : { $gt : yesterday.getTime()}}, 
                            {"_id":"my-sensor-10"} ] } },
    

    现在以每小时间隔对结果进行分组,我会在$group 之前添加一个$project 阶段,以添加一个新字段,在该字段中计算每小时间隔,使用points.timeStamp - yesterday.getTime(),您将获得总差异初始时间和进入时间之间的毫秒数,您可以将其转换为小时并向上舍入到下一个整数值。

    最后,使用来自$project 阶段的新字段,用于$group 阶段。

    【讨论】:

      【解决方案3】:

      您可以使用 mongodb mapReduce 轻松做到这一点。

      试试下面的代码:

      // generate a query to filter result by date and _id.
      // be aware that our query matches documents that contain an array field with 
      // at least one element that matches all the specified criteria.
      var yesterday = new Date();
      yesterday.setDate(yesterday.getDate()-1);
      var query = {"points.timeStamp" : { $gt : yesterday.getTime()}, "_id":"my-sensor-1"};
      
      var map = function(){
          var points = this.points;
          for(var i=0;i<points.length;i++){
              var date = new Date(points[i].timeStamp);
      
              //remove minutes, seconds and milliseconds from the date and emit it
              date.setHours(date.getHours(), 0, 0, 0);
              emit(date, points[i].value);
          }
      };
      
      var reduce = function(key, values){
          //calculate average
          var total = 0;
          for(var i = 0; i < values.length; i++) {
              total += values[i];
          }
          var avg = total / values.length;
          return avg;
      };
      
      db.pointsTest.mapReduce(map, reduce, {out:{inline: 1}, query: query})
      

      【讨论】:

        猜你喜欢
        • 2014-11-22
        • 2014-12-27
        • 1970-01-01
        • 2014-08-24
        • 1970-01-01
        • 2016-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多