Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB Aggregation: How do I recombine a date using $project?

I'm trying to aggregate my data by day so I can render it in a chart. I've managed to do this successfully by grouping by $year, $month, and $dayOfMonth. However, that means that my date is now split into three parts in the final result. Is there a way to concat the three numbers back into a date format or is there another way to group by day that doesn't split date? Below is a working example of what I have:

Sentiment.aggregate([
    {
        $match: { 'content.term' : term_id }
    }, {
        $group: {
            _id: {
                year       : { $year       : '$created_at' },
                month      : { $month      : '$created_at' },
                dayOfMonth : { $dayOfMonth : '$created_at' },
            },
            sum   : { $sum : '$score'},
            count : { $sum : 1 }
        },
    }, {
        $project: {
            _id   : 0,
            date  : '$_id',
            sum   : 1,
            count : 1,
            avg   : { $divide: ['$sum', '$count'] }
        }
    }
], function(err, result){
    if(err) callback(err);
    else callback(null, result);
});

And here is a sample result:

[
  {
    "sum": 201,
    "count": 3,
    "date": {
      "year": 2013,
      "month": 7,
      "dayOfMonth": 5
    },
    "avg": 67
  },
  {
    "sum": 186,
    "count": 6,
    "date": {
      "year": 2013,
      "month": 7,
      "dayOfMonth": 8
    },
    "avg": 31
  },
  {
    "sum": 834,
    "count": 9,
    "date": {
      "year": 2013,
      "month": 7,
      "dayOfMonth": 9
    },
    "avg": 92.66666666666667
  }
]

Ideally, I'd like to have date be a valid date so I don't have to convert it later. I've tried using $concat but that only works with strings. I am using Mongoose if that makes a difference.

like image 442
Isaac Suttell Avatar asked Jul 11 '13 16:07

Isaac Suttell


1 Answers

Assuming that, as you are grouping documents by year, month and day, hours and minutes are useless, you can use one of those operators to get a date sample: $first, $last, $min or $max.

Sentiment.aggregate([
    {
        $match: { 'content.term' : term_id }
    }, {
        $group: {
            _id: {
                year       : { $year       : '$created_at' },
                month      : { $month      : '$created_at' },
                dayOfMonth : { $dayOfMonth : '$created_at' },
            },
            dt_sample : { $first : '$created_at' },
            sum   : { $sum : '$score'},
            count : { $sum : 1 }
        },
    }, {
        $project: {
            _id   : 0,
            date  : '$dt_sample',
            sum   : 1,
            count : 1,
            avg   : { $divide: ['$sum', '$count'] }
        }
    }
], function(err, result){
    if(err) callback(err);
    else callback(null, result);
});

You'll have a date field with an arbitrary hour, minute and seconds.

like image 184
Miguel Cartagena Avatar answered Nov 16 '22 01:11

Miguel Cartagena