Mongo Aggregate: Group and Sort Token error - mongodb

I'm struggling with something which is most likely very stupid on my behalf.
I have a data set in a mongo collection and I wanted to get all the sum of documents grouped by year and month (which I can do fine). However I then want to have those results ordered by year and month.
Here is my query for getting the results with just the sum:
db.xxxx.aggregate([
{
$group: {
_id: { year:
{ $year: "$createdDate" },
month: { $month: "$createdDate" }
},
total_users: { $sum: 1 }
},
}
])
Which results in this:
[
{ _id: { year: 2021, month: 12 }, total_users: 191 },
{ _id: { year: 2021, month: 6 }, total_users: 51 },
{ _id: { year: 2022, month: 3 }, total_users: 165 },
{ _id: { year: 2022, month: 8 }, total_users: 61 },
{ _id: { year: 2021, month: 8 }, total_users: 182 },
{ _id: { year: 2021, month: 11 }, total_users: 81 },
{ _id: { year: 2022, month: 4 }, total_users: 155 },
{ _id: { year: 2022, month: 7 }, total_users: 10 },
{ _id: { year: 2022, month: 5 }, total_users: 31 },
{ _id: { year: 2022, month: 9 }, total_users: 23 },
{ _id: { year: 2021, month: 7 }, total_users: 48 },
{ _id: { year: 2021, month: 10 }, total_users: 75 },
{ _id: { year: 2021, month: 5 }, total_users: 11 },
{ _id: { year: 2022, month: 2 }, total_users: 300 },
{ _id: { year: 2021, month: 9 }, total_users: 131 },
{ _id: { year: 2022, month: 1 }, total_users: 172 },
{ _id: { year: 2022, month: 6 }, total_users: 65 }
]
Now once I try and sort by month and year I get a token error:
db.xxxx.aggregate([
{
$group: {
_id: { year:
{ $year: "$createdDate" },
month: { $month: "$createdDate" }
},
total_users: { $sum: 1 }
},
{$sort: {year:1}},
{$sort: {month:1}}
}
])
Resulting error:
Uncaught:
SyntaxError: Unexpected token (10:4)
8 | total_users: { $sum: 1 }
9 | },
> 10 | {$sort: {year:1}},
| ^
11 |
Atlas [primary] collection> {$sort: {month:1}}
1
Atlas [primary] collection> }
Uncaught:
SyntaxError: Unexpected token (1:0)
> 1 | }
| ^
2 |
Any help would be very welcome!!
Thanks
EDIT
Here's the query now the suggested sort query:
db.xxx.aggregate([
{
$group: {
_id: { year:
{ $year: "$createdDate" },
month: { $month: "$createdDate" }
},
total_users: { $sum: 1 }
},
{
$sort: {
"_id.year": 1,
"_id.month": 1
}
}
}
])
Note I'm still getting the same error

You want to use:
{$sort: {"_id.year": 1, "_id.month": 1}}
See how it works on the playground example
EDIT:
You have misplayed the }. The $group is missing one at the end and the $sort have extra one. It should be:
db.collection.aggregate([
{$group: {
_id: {year: {$year: "$createdDate"}, month: {$month: "$createdDate"}},
total_users: {$sum: 1}
}
},
{$sort: {"_id.year": 1, "_id.month": 1}}
])

Related

How to calculate average records per month?

My records like this [{ createdAt }, {createdAt}, {createdAt} ]
I need average records per month.
january => 3 records
february => 2 records etc..
You can try to $group by month and year when counting and by month when averaging:
db.collection.aggregate([
{
$group: {
_id: {
month: {
$month: "$createdAt"
},
year: {
$year: "$createdAt"
},
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: {
month: "$_id.month"
},
average: {
$avg: "$count"
}
}
},
{
$project: {
_id: 0,
month: "$_id.month",
average: 1
}
}
])
Link to playground
Not fully clear what you mean by "average records per month" but I think it would be this:
db.collection.aggregate([
{
$group: {
_id: {
$dateTrunc: {
date: "$createdAt",
unit: "month"
}
},
count: { $count: {} }
}
},
{
$group: {
_id: null,
data: { $push: { k: { $toString: { $month: "$_id" } }, v: "$count" } }
}
},
{ $replaceWith: { $arrayToObject: "$data" } }
])
Getting the month name is not so easy, either you use a external library or build your own with $switch

MongoDB group results by time interval

I have a collection like below.
{
"field1":"value1",
"created_at":"2022-01-01T11:42:01Z"
},
{
"field1":"value2",
"created_at":"2022-01-01T11:22:15Z"
}
I need to group the results by 15 minute time interval and project the results like below from this collection.
[{
"from":"2022-01-01T11:15:00Z",
"to":"2022-01-01T11:30:00Z",
"count":1
},
{
"from":"2022-01-01T11:30:00Z",
"to":"2022-01-01T11:45:00Z",
"count":1
}]
I am able to get the count by 15 minute time interval using the below query. But I want to project from and to dates as well.
db.collection.aggregate([
{ "$group": {
"_id": {
"year": { "$year": "$created_at" },
"dayOfYear": { "$dayOfYear": "$created_at" },
"hour": { "$hour": "$created_at" },
"interval": {
"$subtract": [
{ "$minute": "$created_at" },
{ "$mod": [{ "$minute": "$created_at"}, 15] }
]
}
}},
"count": { "$sum": 1 }
}}
])
You can try an approach,
$dateToParts get parts of the created_at date
$group by year, month, day, hour, and interval as per mod and subtraction calculation and get the total count
to get from and to date from interval you can use $dateFromParts operator, just to add 15 minutes into the date.
db.collection.aggregate([
{
$addFields: {
created_at: { $dateToParts: { date: "$created_at" } }
}
},
{
$group: {
_id: {
year: "$created_at.year",
month: "$created_at.month",
day: "$created_at.day",
hour: "$created_at.hour",
interval: {
$subtract: [
"$created_at.minute",
{ $mod: ["$created_at.minute", 15] }
]
}
},
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
count: 1,
from: {
$dateFromParts: {
year: "$_id.year",
month: "$_id.month",
day: "$_id.day",
hour: "$_id.hour",
minute: "$_id.interval"
}
},
to: {
$dateFromParts: {
year: "$_id.year",
month: "$_id.month",
day: "$_id.day",
hour: "$_id.hour",
minute: { $add: ["$_id.interval", 15] }
}
}
}
}
])
Playground

How to group MongoDB aggregation [duplicate]

Who knows a better solution to group Orders by date and sum total and count by source. Of course I can group by Source and then I get only totals for this source only, I can alter the result thereafter to get the desired result. But I would like to know if it is possible in one simple $group statement.
Eg. ordersByApp = 1, ordersByWEB = 2
Orders collection
{
_id: 'XCUZO0',
date: "2020-02-01T00:00:03.243Z"
total: 9.99,
source: 'APP'
},
{
_id: 'XCUZO1',
date: "2020-01-05T00:00:03.243Z"
total: 9.99,
source: 'WEB'
},
{
_id: 'XCUZO2',
date: "2020-01-02T00:00:03.243Z"
total: 9.99,
source: 'WEB'
}
My current aggregation
Order.aggregate([
{
$group: {
_id: {
month: { $month: "$date",
year: { $year: "$date" }
},
total: {
$sum: "$total"
}
}
}
])
Current result
[
{
_id: { month: 01, year: 2020 },
total: 19.98
},
{
_id: { month: 02, year: 2020 },
total: 9.99
}
]
Desired result, How can I achieve the below?
[
{
_id: { month: 01, year: 2020 },
total: 19.98,
countByApp: 1, <---
countByWEB: 0, <---
},
{
_id: { month: 02, year: 2020 },
total: 9.99,
countByWEB: 2, <---
countByAPP: 0 <---
}
]
You can use $cond like below:
Order.aggregate([
{
$group: {
_id: {
month: { $month: "$date" },
year: { $year: "$date" }
},
total: { $sum: "$total" },
countByApp: { $sum: { $cond: [ {$eq: [ "$source", "APP" ]} , 1, 0] } },
countByWeb: { $sum: { $cond: [ {$eq: [ "$source", "WEB" ]} , 1, 0] } },
}
}
])
Mongo Playground

MongoDB Aggregation by Shifted Date

Is there a way to aggregate by day, but over a 24 hour period that does not go from 12am - 11:59pm? A sample document looks like this:
{
date: ISODate("2012-11-02T17:04:11.102Z"),
user: 'testUser',
orders: 50
}
I need to aggregate the # of orders per user per day between 5 pm and 4:59:59.999 pm the next day. I can get the # of orders per user per day (over a 2 day range) using this:
db.hs.aggregate([{
$match: {
user: 'testUser',
date: {
$gte: new Date(2015,0,4,17,0,0,0),
$lt: new Date(2015,0,6,17,0,0,0)
}
}
}, {
$group: {
_id: {
date: {
month: {$month: "$date"},
day: {$dayOfMonth: "$date"},
year: {$year: "$date"}
},
user: "$user",
},
totord: {$sum: "$orders"}
}
}])
But this returns 3 results, one for Jan 4 after 5pm, one for Jan 5 all day, and one for Jan 6 before 5pm. I just don't know how to shift the aggregation to be between 5pm - 4:59pm.
I believe that you could use the $add aggregation operator on your dates in a $project aggregation step to get the desired result.
db.hs.aggregate([
{
$match: {
user: 'testUser',
date: {
$gte: new Date(2015,0,4,17,0,0,0),
$lt: new Date(2015,0,6,17,0,0,0)
}
}
}, {
$project: {
orders: 1,
user: 1,
date: { $add: [ "$date", 7*60*60000 ] }
}
}, {
$group: {
_id: {
date: {
month: { $month: "$date" },
day: { $dayOfMonth: "$date" },
year: { $year: "$date" }
},
user: "$user"
},
totord: {$sum: "$orders"}
}
}
])
I believe this should add 7 hours to the $date which should make it so anything after 5PM ends up after midnight the next day.

Why does this Mongo Aggregation query not result in a properly ordered set?

I have a collection of documents that look like the following:
{
user: "D1D2A08B-7242-4415-BA4F-442B18DBD2ED-2463-00000074A82D0518",
language: "Cantonese",
imageFileName: "01D28C90-DB71-40E2-96A2-A934ABC54815-2695-0000043BD1ECAB22",
audioFileName: "76CD717B-1A57-412C-8973-3518D72B45AD-8996-000008855B90D78A",
date: ISODate("2013-04-04T18:24:25.753Z"),
correct: 1,
_id: ObjectId("515dc559cd9d87de5a000018")
}
The following query performs an aggregation that I expect would be ordered by the ISODate element:
var getUserStats = function(user, language, callback) {
var guessCollection = db.collection('Guesses');
guessCollection.aggregate(
{ $match: {
user: user,
language: language,
}},
{ $sort: {
date: 1
}},
{ $project : {
user : 1,
language : 1,
year : { $year: '$date' },
month : { $month: '$date' },
day : { $dayOfMonth: '$date'},
correct : 1,
guesses : 1
} },
{ $group : {
_id : { year: "$year", month: "$month", day: "$day" },
correct : { $sum : "$correct" },
guesses: { $sum : 1 }
} }
, function(err, result){
console.log(result);
callback(err, result);
});
Here's a sample query result:
[{
_id: {
year: 2013,
month: 6,
day: 8
},
correct: 11,
guesses: 17
}, {
_id: {
year: 2013,
month: 6,
day: 7
},
correct: 11,
guesses: 15
}, {
_id: {
year: 2013,
month: 6,
day: 5
},
correct: 35,
guesses: 48
}, {
_id: {
year: 2013,
month: 6,
day: 6
},
correct: 69,
guesses: 96
}, {
_id: {
year: 2013,
month: 5,
day: 25
},
correct: 2,
guesses: 3
...
As you can see, the results of the query are not ordered by date. Can someone suggest what I may have done incorrectly?
A group can be unordered. What you can do to sort it correctly is do another sort on the _id after you have grouped.