MongoDB:有没有办法使用聚合检测价值趋势?

时间:2015-02-05 03:23:16

标签: mongodb mapreduce mongodb-query aggregation-framework

我正在尝试检测集合中值的“趋势”。

假设我有以下内容:

{ created_at: 2014-12-01, value:1015 }
{ created_at: 2014-12-01, value:1015 }
{ created_at: 2014-12-01, value:1019 }
{ created_at: 2014-12-02, value:1018 }
{ created_at: 2014-12-02, value:1021 }
{ created_at: 2014-12-03, value:1010 }
{ created_at: 2014-12-03, value:1012 }
{ created_at: 2014-12-03, value:1011 }
{ created_at: 2014-12-04, value:1012 }

我只想要输出如下:

{ created_at: 2014-12-01, average: 1016, diff: 0}
{ created_at: 2014-12-02, average: 1019, diff: 3}

diff是两者之间平均值的差值 这两个日期。

我想出了如何计算平均值,找出集合中的最小/最大值和第一个/最后一个值,但找不到比较两个平均值的方法......

2 个答案:

答案 0 :(得分:1)

根据您对"每分钟聚合"的评论,假设这些是实际日期。在一次传递中唯一真正的方法是使用mapReduce。这里的关键是mapReduce可以存储一个全局变量,因此" track"你的最后一个结果,以确定"差异"在每个汇总记录之间

db.collection.mapReduce(
    function() {
        // Round date to the minute
        var key = this.created_at.valueOf()
            - ( this.created_at.valueOf() % ( 1000 * 60 ) );
        emit( key, { "average": this.value } );
    },
    function(key,values) {
        values = values.map(function(i) { return i.average });
        var result = {
           "average": Math.floor(Array.avg(values))
        };
        return result;
    },
    {
        "out": { "inline": 1 },
        "scope": { "lastAvg": 0 },
        "finalize": function(key,value) {
            value.diff = ( lastAvg == 0 ) ? 0 : value.average - lastAvg;
            lastAvg = value.average;
            return value;
        }
    }
)

或者你可以"后处理"正如已经提到的那样,在客户端代码中执行相同的操作来计算差异,因为您使用类似的范围变量迭代游标。作为shell示例:

var lastAvg = 0;
db.collection.aggregate([
    { "$group": {
        "_id": { "$subtract": [
            { "$subtract": [ "$created_date", new Date(0) ] },
            { "$mod": [
                { "$subtract": [ "$created_date", new Date(0) ] },
                1000 * 60
            ]}
        ]},
        "average": { "$avg": "$value" }
    }},
    { "$sort": { "_id": 1 } }
]).forEach(function(doc) {
    doc.average = Math.floor(doc.average);
    doc.diff = ( lastAvg == 0 ) ? 0 : doc.average - lastAvg;
    lastAvg = doc.average;
    printjson(doc);
})

在这两种情况下,我都使用日期数学原理,以便将日期对象转换为unix / epoch时间戳表示形式,作为一个数字,该数字逐个舍入到它最近的分钟。使用聚合框架,您可以使用date aggregation operators提取日期部分进行分组。

在任何一种情况下,将Date对象重新转换为内部.mapReduce()内部或使用.aggregate()进行后期处理非常简单。

所以在结束时,您可以使用"全局范围" mapReduce的功能,或者您可以只从聚合处理结果游标,以计算结果中每个分组之间的差异。

答案 1 :(得分:0)

粗略轮廓:我会计算十分钟的平均值:

> var avgCursor = db.sensor_readings.aggregate([
    { "$match" : { "created_at" : { "$gt" : ten_minutes_ago, "$lte" : now } } }
    { "$group" : { "_id" : 0, "average" : { "$avg" : "$value" } } }
]}
> var avgDoc = avgCursor.toArray()[0]
> avgDoc
{ "_id" : 0, "average" : 23 }

然后我将它存储在另一个集合中:

> db.sensor_averages.insert({ "start" : ten_minutes_ago, "end" : now, "average" : avgDoc.average })

最后,回想一下计算差异所需的两个平均值,然后计算它:

> var diffCursor = db.sensor_averages.find({ "start" : { "$gte" : twenty_minutes_ago } }).sort({ "start" : -1 })
> var diffArray = diffCursor.toArray()
> var difference = diffArray[0].average - diffArray[1].average

您还可以跳过定期聚合,而是在sensor_averages中更新运行平均值,每10分钟跳转到一个新文档。在每10分钟开始时,插入sensor_averages文档

{
    "start" : now,
    "svalues" : 0,
    "nvalues" : 0
}

然后在接下来十分钟的sensor_reading文档的每次插入中,还更新sensor_averages doc:

db.sensor_averages.update(
    { "start" : now_rounded_to_the_ten_minute_boundary },
    { "$inc" : { "svalues" : value, "nvalues" : 1 } }
)

然后,当您想要平均值之间的差异时,请调用相应的两个文档,将svalues除以nvalues以获得平均值,然后减去。