【问题标题】:Mongodb Aggregation by Day then HourMongodb 按天和小时聚合
【发布时间】:2014-08-10 00:51:02
【问题描述】:

我正在使用 mongodb 聚合来聚合一组数据。我的情况有点复杂。我的收藏如下:

{
  startTime: ISODate("2014-12-31T10:20:30Z"),
  customerId: 123,
  ping: "2",
  link: "3"
}

现在我想将数据聚合到另一个集合,如下所示:

{
_id: {
 day: ISODate("2014-12-31T00:00:00Z"),
 customerId: 123
 },
hours: [
  {
   hour: ISODate("2014-12-31T10:00:00Z"),
   pings: 2,
   links: 3
  },
  {
   hour: ISODate("2014-12-31T11:00:00Z"),
   pings: 5,
   links: 6
  }
 ]
}

如您所见,数据首先按天分组,然后按小时分组。我有以下聚合查询按天对它们进行分组,但是如何按小时对它们进一步分组?有什么想法吗?

var pipeline = [
{
 $project : {  
       startTime : 1,
               customerId: 1,
       ping:1,
       link:1,
       date : "$startTime",  
       h : {  
            "$hour" : "$startTime"  
       },  
       m : {  
            "$minute" : "$startTime"  
       },  
       s : {  
            "$second" : "$startTime"  
       },  
       ml : {  
            "$millisecond" : "$startTime"  
       }  
  }
},
{
$project: {
    startTime : 1,
            customerId: 1,
    ping:1,
    link:1,
      date : {      
            "$subtract" : [      
                 "$date",      
                 {      
                      "$add" : [      
                           "$ml",      
                           {      
                                "$multiply" : [      
                                     "$s",      
                                     1000      
                                ]      
                           },      
                           {      
                                "$multiply" : [      
                                     "$m",      
                                     60,      
                                     1000      
                                ]      
                           },
                           {      
                                "$multiply" : [      
                                     "$h",      
                                     60,      
                                     60,      
                                     1000 
                                ]      
                           }      
                      ]      
                 }      
            ]      
       }
    }          
},
{
    $match: {
        "startTime": {
            $gte: new ISODate("2013-12-01T07:00:00Z"),
            $lte: new ISODate("2014-01-01T08:00:00Z"),
        }
    }
},
// Aggregate the data
{
    $group: {
        _id: {day : "$date", customerId: "$customerId"},
        pings : {$sum: "$ping"},
        links : {$sum: "$links"}
    }
}
];

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    您基本上想要的是双重分组,但您没有使用date aggregation operators 获取整个日期对象,只是相关部分:

    db.collection.aggregate([
        { "$group": {
            "_id": {
                "customerId": "$customerId",
                "day": { "$dayOfYear": "$startTime" },
                "hour": { "$hour": "$startTime" }
            },
            "pings": { "$sum": "$ping" },
            "links": { "$sum": "$link" }
        }},
        { "$group": {
           "_id": {
               "customerId": "$_id.customerId",
               "day": "$_id.day"
           },
           "hours": { 
               "$push": { 
                   "hour": "$_id.hour",
                   "pings": "$pings",
                   "links": "$links"
               }
           }
        }}
    ])
    

    双精度 $group 通过每天将结果放入数组中为您提供所需的格式。示例中的单个文档,但您基本上会得到这样的结果:

    {
        "_id" : {
                "customerId" : 123,
                "day" : 365
        },
        "hours" : [
                {
                        "hour" : 10,
                        "pings" : 2,
                        "links" : 3
                }
        ]
    }
    

    如果您发现日期运算符的结果难以处理或希望为日期对象提供简化的“传递”结果,那么您可以转换为纪元时间戳:

    db.collection.aggregate([
        { "$group": {
            "_id": {
                "customerId": "$customerId",
                "day": {
                   "$subtract": [
                       { "$subtract": [ "$startTime", new Date("1970-01-01") ] },
                       {
                           "$mod": [
                               { "$subtract": [ "$startTime", new Date("1970-01-01") ] },
                               1000*60*60*24   
                           ]
                       }
                   ]
                },
                "hour": {
                   "$subtract": [
                       { "$subtract": [ "$startTime", new Date("1970-01-01") ] },
                       {
                           "$mod": [
                               { "$subtract": [ "$startTime", new Date("1970-01-01") ] },
                               1000*60*60   
                           ]
                       }
                   ]
                }
            },
            "pings": { "$sum": "$ping" },
            "links": { "$sum": "$link" }
        }},
        { "$group": {
           "_id": {
               "customerId": "$_id.customerId",
               "day": "$_id.day"
           },
           "hours": { 
               "$push": { 
                   "hour": "$_id.hour",
                   "pings": "$pings",
                   "links": "$links"
               }
           }
        }}
    ])
    

    其中的诀窍是,当您 $subtract 一个日期对象来自另一个对象时,您会得到“纪元”值作为结果。在这种情况下,我们使用“纪元”开始日期来获取整个时间戳值,并仅提供“日期数学”以将时间更正为所需的时间间隔。所以结果:

    {
        "_id" : {
                "customerId" : 123,
                "day" : NumberLong("1419984000000")
        },
        "hours" : [
                {
                        "hour" : NumberLong("1420020000000"),
                        "pings" : 2,
                        "links" : 3
                }
        ]
    }
    

    根据您的需要,这可能比日期运营商提供的结果更适合您。

    您还可以通过 $let 运算符在 MongoDB 2.6 中为此添加一些简写,允许您为作用域操作声明“变量”:

    db.event.aggregate([
        { "$group": {
            "_id": {
                "$let": {
                    "vars": { 
                       "date": { "$subtract": [ "$startTime", new Date("1970-01-01") ] },
                       "day": 1000*60*60*24,
                       "hour": 1000*60*60
                    },
                    "in": {
                        "customerId": "$customerId",
                        "day": {
                            "$subtract": [
                                "$$date",
                                { "$mod": [ "$$date", "$$day" ] }
                             ]
                        },
                        "hour": {
                            "$subtract": [
                                "$$date",
                                { "$mod": [ "$$date", "$$hour" ] }
                             ]
                        }
                    }
                }
            },
            "pings": { "$sum": "$ping" },
            "links": { "$sum": "$link" }
        }},
        { "$group": {
           "_id": {
               "customerId": "$_id.customerId",
               "day": "$_id.day"
           },
           "hours": { 
               "$push": { 
                   "hour": "$_id.hour",
                   "pings": "$pings",
                   "links": "$links"
               }
           }
        }}
    ])
    

    另外,我差点忘了提及“ping”和“link”的值实际上是字符串,除非那是一个错字。但如果不是,请确保先将它们转换为数字。

    【讨论】:

    • 谢谢。你能帮我进一步吗?我有一个调度程序,它将每小时运行一次以聚合上一小时的数据,如下所示:{"_id" : { "customerId" : 123, "day" : ISODate("2012-06-20:00:00:00Z")}, "hours" : [{"hour" : ISODate("2012-06-20:01:00:00Z"), "pings" : 2,"links" : 3}]} 在上面运行聚合查询之后,我需要将其合并/更新到此集合以存储聚合数据。我怎样才能做到这一点?谢谢
    • @user3756522 这听起来确实像另一个问题,最好在一篇新帖子中这样问,您可以在其中正确解释您的意图,而不是在 cmets 中。答案中显示的查询将按您发送的范围每天和每小时汇总。此外,您的 $match 应该始终是管道的 first 阶段。使用 MongoDB 2.6,您的聚合输出可以是您迭代以处理结果的游标
    猜你喜欢
    • 2020-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-26
    • 2021-07-21
    • 1970-01-01
    • 1970-01-01
    • 2017-12-25
    相关资源
    最近更新 更多