按时间窗口之间的时间间隔分组

时间:2019-02-20 21:30:39

标签: mongodb aggregation-framework

我具有以下文档结构:

{  
   "_id":"5c59c35d8610f702d00e6f70",
   "ipAddress":"50.116.14.48",
   "startTime":"2018-02-06T12:01:59.000Z",
   "endTime":"2018-02-06T12:31:00.000Z", 
}

我希望能够在15分钟的时间范围内对事件进行分组。例如;鉴于以上文档的结构,我认为该文档将在0-15分钟,15-30分钟和30-45分钟内都算作一次事件。

结果看起来像这样:

[  
   {  
      "occurrences":1,
      "startWindow":"2018-02-06T12:00:00.000Z",
      "endWindow":"2018-02-06T12:15:00.000Z"
   },
   {  
      "occurrences":1,
      "startWindow":"2018-02-06T12:15:01.000Z",
      "endWindow":"2018-02-06T12:30:00.000Z"
   },
   {  
      "occurrences":1,
      "startWindow":"2018-02-06T12:30:01.000Z",
      "endWindow":"2018-02-06T12:45:00.000Z"
   }
]

我看过很多例子,它们仅按一个时间间隔对一个日期进行分组,但是这种情况下文档有时间窗呢?

您如何建立此聚合?

2 个答案:

答案 0 :(得分:2)

如果您可以以毫秒为单位而不是字符串进行操作,则很容易。要将startTimeendTime转换为毫秒数,可以使用$toDate$toLong运算符(MongoDB 4.0或更高版本)。

将您的日期“分类”到15分钟范围的公式也非常简单:15分钟等于900000毫秒,因此您可以使用$mod来获取需要从原始日期开始$subtract编辑。

然后,您可以使用$unwind然后使用$group从每个文档生成两个文档(分别为startDateendDate)。

db.col.aggregate([
    {
        $project: {
            startTime: { $toLong: { $toDate: "$startTime" } },
            endTime: { $toLong: { $toDate: "$endTime" } }
        }
    },
    {
        $project: {
            ranges: [
                { 
                    start: { $subtract: [ "$startTime", { $mod: [ "$startTime", 900000 ] } ] },
                    end: { $add: [ { $subtract: [ "$startTime", { $mod: [ "$startTime", 900000 ] } ] }, 900000 ] }
                },
                { 
                    start: { $subtract: [ "$endTime", { $mod: [ "$endTime", 900000 ] } ] },
                    end: { $add: [ { $subtract: [ "$endTime", { $mod: [ "$endTime", 900000 ] } ] }, 900000 ] }
                }
            ]
        }
    },
    {
        $unwind: "$ranges"
    },
    {
        $group: {
            _id: "$ranges",
            count: { $sum: 1 }
        }
    },
    {
        $project: {
            _id: 0,
            occurences: "$count",
            startWindow: { $toString: { $toDate: "$_id.start" } },
            endWindow: { $toString: { $toDate: "$_id.end" } }
        }
    }
])

答案 1 :(得分:2)

除了米克尔的时间数学方法外,您还需要使用$range在开始和结束之间的所有“窗口”中“散布”文档:

db.col.aggregate([
    { $addFields: {
        // an array of 15 min intervals between startTime and endTime
        window: { $range: [ 
            { $floor: { $divide: [ { $toLong: { $toDate: "$startTime" } }, 900000 ] }  }, 
            { $ceil: { $divide: [ { $toLong: { $toDate: "$endTime" } }, 900000 ] }  }
        ] }
    } },
    // 1 document per interval
    { $unwind: "$window" },
    // group by interval
    { $group: {
        _id: "$window",
        occurrences: { $sum: 1 }
    }},
    // to match expected order
    {$sort: {_id:1}},
    // calculate window boundaries
    { $project: {
        _id: 0,
        occurrences: 1,
        startWindow: { $toDate: { $add: [ { $multiply: [ "$_id", 900000 ] }, 1000 ] } },
        endWindow: { $toDate: { $multiply: [ { $add: [ "$_id", 1 ] }, 900000 ] } }        
    } }
])