While ObjectId is very useful - its first four bytes are the timestamp when it was generated - there's a simple way to convert it to a full date in Javascript (like mongo shell) but there is no way to convert it to a timestamp in aggregation pipeline (although there is a request for such a feature).
Since we can't do it in aggregation natively, we can use a stupid trick to generate "YEAR-MONTH" from ObjectId during $project stage so that we can group by it. Here is how I did it.
Working in the shell, first I generated an array of objects which represent all the months I want to report for (so I only generated a few years worth of months):
var d = []; var o = []; var pad="f000000000000000"; for (yr=2014; yr < 2017; yr++ ) { for (m=1; m<13; m++) { if (m<10) mo="0"+m; else mo=""+m; var dt=new ISODate(""+yr+"-"+mo+"-01T00:00:00Z"); d.push(""+yr+"-"+mo); o.push(new ObjectId( (dt.getTime()/1000).toString(16)+pad)); } }
Now I can create a shell function which takes a field and two arrays, and creates an expression we can use in $project stage to map ranges in the second array to labels in the first array:
makeLabeledBuckets=function( field, keys, values) { var con=[]; var maxPos=keys.length; con[maxPos]=">" + keys[maxPos-1]; for (pos = maxPos-1; pos > 0; pos--) { con[pos] = {"$cond":{ if: {$lt:[field, values[pos]]}, then: keys[pos-1], else: con[pos+1] }}; } var first = "< " + keys[0]; con[0]={"$cond":{if: {$lt:[field,values[0]]}, then: first, else: con[1] }}; return con[0]; }
> db.collection.aggregate( [ { $project: { yearMonthStr: makeLabeledBuckets("$_id", d, o) } } ] ) { "_id" : ObjectId("55af2194cd214aaa0a5e3545"), "yearMonth" : "2015-08" } { "_id" : ObjectId("55af21b5cd214aaa0a5e3548"), "yearMonth" : "2015-08" } { "_id" : ObjectId("55aff3f78909abe4721284bc"), "yearMonth" : "2015-08" } { "_id" : ObjectId("55aff4bd8909abe4721284c0"), "yearMonth" : "2015-08" } { "_id" : ObjectId("56900c440172f6f5768fb249"), "yearMonth" : "2016-02" } { "_id" : ObjectId("56900d780172f6f5768fb24c"), "yearMonth" : "2016-02" } { "_id" : ObjectId("56900dc80172f6f5768fb24e"), "yearMonth" : "2016-02" } { "_id" : ObjectId("569014240172f6f5768fb251"), "yearMonth" : "2016-02" }