Агрегация Mongodb по дням и часам

Я использую агрегацию 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"}
    }
}
];

1 ответов


то, что вы в основном хотите, - это двойная группировка, но вы не получаете весь объект date обратно с помощью дата операторы агрегирования, только соответствующие части:

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
            }
    ]
}

что может быть более приемлемым для Вас, чем дата операторы предоставляют в результате в зависимости от ваших потребностей.

вы также можете добавить немного стенографии для этого с MongoDB 2.6 через $let оператор, который позволяет объявлять "переменные" для операций с областью действия:

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" на самом деле являются строками, если это не опечатка. Но если нет, тогда убедитесь, что вы сначала конвертируете их в числа.