This can be done quite simply using the aggregation framework. Which is not just for "summing" values, but also excels at document re-shaping.
Which is your best option as though the "non-aggregating" code can look a little simpler using mapReduce, as that process relies on a JavaScript interpreter, as opposed to the native code of the aggregation framework, and a mapReduce will therefore run much slower. Considerably so over large data.
db.collection.aggregate([
// Place all items in an array by year and month
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
},
"rounds": { "$push": {
"user": "$user",
"date": "$date",
"course": "$course",
"score": "$score",
"_id": "$_id"
}}
}},
// Sort the results by year and month
{ "$sort": { "_id.year": 1, "_id.date": 1 } },
// Optional project to your exact form
{ "$project": {
"_id": 0,
"year": "$_id.year",
"month": "$_id.month",
"rounds": 1
}}
])
Or possibly without the arrays you specify and leave everything just in a flat form:
db.collection.aggregate([
{ "$project": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
"user": 1,
"date": 1,
"course": 1,
"score": 1
}},
{ "$sort": { "year": 1, "month": 1 } }
])
Or even do it to exactly how you specify with the month names:
db.collection.aggregate([
// Place all items in an array by year and month
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
},
"rounds": { "$push": {
"user": "$user",
"date": "$date",
"course": "$course",
"score": "$score",
"_id": "$_id"
}}
}},
// Sort the results by year and month
{ "$sort": { "_id.year": 1, "_id.date": 1 } },
// Optionally project to your exact form
{ "$project": {
"_id": 0,
"year": "$_id.year",
"month": { "$cond": [
{ "$eq": [ "$_id.month": 1 ] },
"January",
{ "$cond": [
{ "$eq": [ "$_id.month": 2 ] },
"February",
{ "$cond": [
{ "$eq": [ "$_id.month": 3 ] },
"March",
{ "$cond": [
{ "$eq": [ "$_id.month": 4 ] },
"April",
{ "$cond": [
{ "$eq": [ "$_id.month": 5 ] },
"May",
{ "$cond": [
{ "$eq": [ "$_id.month": 6 ] },
"June",
{ "$cond": [
{ "$eq": [ "$_id.month": 7 ] },
"July",
{ "$cond": [
{ "$eq": [ "$_id.month": 8 ] },
"August",
{ "$cond": [
{ "$eq": [ "$_id.month": 9 ] },
"September",
{ "$cond": [
{ "$eq": [ "$_id.month": 10 ] },
"October",
{ "$cond": [
{ "$eq": [ "$_id.month": 11 ] },
"November",
"December"
]}
]}
]}
]}
]}
]}
]}
]}
]}
]}
]},
"rounds": 1
}}
])
Which is overkill of course, but just to show that it can be done.
And there is of course the Operator reference so you can understand the usages of the operators used. And you may well want to alter this by adding your own $match
condition to restrict the date range that you are looking at. Or indeed for other purposes.