Mongo聚合在一段时间内

时间:2015-07-21 22:13:08

标签: mongodb aggregation-framework

我有一些存储在mongo集合中的日志数据,其中包含作为request_id的基本信息以及它添加到集合中的时间,例如:

{
    "_id" : ObjectId("55ae6ea558a5d3fe018b4568"),
    "request_id" : "030ac9f1-aa13-41d1-9ced-2966b9a6g5c3",
    "time" : ISODate("2015-07-21T16:00:00.00Z")
}

我想知道我是否可以使用聚合框架来聚合一些统计数据。我想得到在最后X小时的每个N分钟间隔内创建的对象的计数。

因此,过去1小时我需要10分钟间隔的输出应该如下所示:

{ "_id" : 0, "time" : ISODate("2015-07-21T15:00:00.00Z"), "count" : 67 }
{ "_id" : 0, "time" : ISODate("2015-07-21T15:10:00.00Z"), "count" : 113 }
{ "_id" : 0, "time" : ISODate("2015-07-21T15:20:00.00Z"), "count" : 40 }
{ "_id" : 0, "time" : ISODate("2015-07-21T15:30:00.00Z"), "count" : 10 }
{ "_id" : 0, "time" : ISODate("2015-07-21T15:40:00.00Z"), "count" : 32 }
{ "_id" : 0, "time" : ISODate("2015-07-21T15:50:00.00Z"), "count" : 34 }

我会用它来获取图表的数据。

感谢任何建议!

3 个答案:

答案 0 :(得分:4)

根据哪种输出格式最适合您的需要,有几种方法可以解决这个问题。主要注意事项是,使用"aggregation framework"本身,您实际上无法将某些“强制转换”作为日期返回,但在处理API中的结果时,您可以获得可轻松重建为Date对象的值。

第一种方法是使用聚合框架可用的"Date Aggregation Operators"

db.collection.aggregate([
    { "$match": {
        "time": { "$gte": startDate, "$lt": endDate }
    }},
    { "$group": {
        "_id": {
            "year": { "$year": "$time" },
            "dayOfYear": { "$dayOfYear": "$time" },
            "hour": { "$hour": "$time" },
            "minute": {
                "$subtract": [
                    { "$minute": "$time" },
                    { "$mod": [ { "$minute": "$time" }, 10 ] }
                ]
            }
        },
        "count": { "$sum": 1 }
    }}
])

返回_id的复合键,其中包含“日期”所需的所有值。或者,如果仅在“小时”内,则只需使用“分钟”部分,并根据范围选择的startDate计算出实际日期。

或者你可以使用普通的“日期数学”来获得自“epoch”以来的毫秒数,这可以再次直接输入日期构造函数。

db.collection.aggregate([
    { "$match": {
        "time": { "$gte": startDate, "$lt": endDate }
    }},
    { "$group": {
        "_id": {
            "$subtract": [
               { "$subtract": [ "$time", new Date(0) ] },
               { "$mod": [
                   { "$subtract": [ "$time", new Date(0) ] },
                   1000 * 60 * 10
               ]}
            ]
        },
        "count": { "$sum": 1 }
    }}
])

在所有情况下,想要做的是在实际应用$project之前使用$group。作为“管道阶段”,$project必须“循环”,但所有文档都会被选中并“转换”内容。

这需要时间,并添加到查询的执行总计。您只需直接申请$group即可。

或者如果你真的“纯粹”关于在没有后期处理的情况下返回的Date对象,那么你总是可以使用"mapReduce",因为JavaScript函数实际上允许重铸为日期,但比聚合框架,当然没有游标响应:

db.collection.mapReduce(
   function() {
       var date = new Date(
           this.time.valueOf() 
           - ( this.time.valueOf() % ( 1000 * 60 * 10 ) )
       );
       emit(date,1);
   },
   function(key,values) {
       return Array.sum(values);
   },
   { "out": { "inline": 1 } }
)

您最好的选择是使用聚合,因为转换响应非常简单:

db.collection.aggregate([
    { "$match": {
        "time": { "$gte": startDate, "$lt": endDate }
    }},
    { "$group": {
        "_id": {
            "year": { "$year": "$time" },
            "dayOfYear": { "$dayOfYear": "$time" },
            "hour": { "$hour": "$time" },
            "minute": {
                "$subtract": [
                    { "$minute": "$time" },
                    { "$mod": [ { "$minute": "$time" }, 10 ] }
                ]
            }
        },
        "count": { "$sum": 1 }
    }}
]).forEach(function(doc) {
    doc._id = new Date(doc._id);
    printjson(doc);
})

然后,您将区间分组输出与实际Date个对象。

答案 1 :(得分:1)

这样的东西?

pipeline = [
    {"$project":
        {"date": {
            "year": {"$year": "$time"},
            "month": {"$month": "$time"},
            "day": {"$dayOfMonth": "$time"},
            "hour": {"$hour": "$time"},
            "minute": {"$subtract": [
                {"$minute": "$time"},
                {"$mod": [{"$minute": "$time"}, 10]}
            ]}
        }}
    },
    {"$group": {"_id": "$date", "count": {"$sum": 1}}}
]

示例:

> db.foo.insert({"time": new Date(2015,  7, 21, 22, 21)})
> db.foo.insert({"time": new Date(2015,  7, 21, 22, 23)})
> db.foo.insert({"time": new Date(2015,  7, 21, 22, 45)})
> db.foo.insert({"time": new Date(2015,  7, 21, 22, 33)})
> db.foo.aggregate(pipeline)

并输出:

{ "_id" : { "year" : 2015, "month" : 8, "day" : 21, "hour" : 20, "minute" : 40 }, "count" : 1 }
{ "_id" : { "year" : 2015, "month" : 8, "day" : 21, "hour" : 20, "minute" : 20 }, "count" : 2 }
{ "_id" : { "year" : 2015, "month" : 8, "day" : 21, "hour" : 20, "minute" : 30 }, "count" : 1 }

答案 2 :(得分:0)

代替具体答案的指针。你可以使用date aggregations在几分钟,几小时和一段时间内轻松完成。每10分钟会有点棘手,但可能会有一些争吵。然而,作为大数据集的坚果,聚合将会很慢。

我建议在插入后提取分钟

{
    "_id" : ObjectId("55ae6ea558a5d3fe018b4568"),
    "request_id" : "030ac9f1-aa13-41d1-9ced-2966b9a6g5c3",
    "time" : ISODate("2015-07-21T16:00:00.00Z"),
    "minutes": 16
}

尽管添加四分位数和六分相或N可能是完全荒谬的。

{
    "_id" : ObjectId("55ae6ea558a5d3fe018b4568"),
    "request_id" : "030ac9f1-aa13-41d1-9ced-2966b9a6g5c3",
    "time" : ISODate("2015-07-21T16:00:00.00Z"),
    "minutes": 16,
    "quartile: 1,
    "sextile: 2,
}

首先尝试在会议记录上执行$div。并不是ceil和地板。但请查看

Is there a floor function in Mongodb aggregation framework?