Group documents with MongoDB - mongodb

I come from the world of relational databases and am having difficulty with a MongoDB query.
In my database I have telemetry documents with minute-by-minute records for each monitored system.
I'm tending to do a query that groups records by hour.
Below is a demonstration of the structure of my documents:
{
_id: ObjectId("61847b83618fc8a6fb368ab7"),
system_code: 22,
date: ISODate("2021-08-01T00:00:01.000Z"),
value1: 22.2372973251,
value2: 20
}
Here's the structure of the query I'm trying to run:
db.telemetry.aggregate([
{
$match: {
$and: [
{ system_code: 22 },
{ date: { $gte: ISODate("2021-08-01 00:00:00") } },
{ date: { $lte: ISODate("2021-08-01 02:00:00") } }
]
}
},
{
$addFields: {
italianDate: {
$dateToString: {
format: "%d/%m/%Y %H:00:00",
date: "$date"
}
}
}
},
{
$sort: {
italianDate: 1
}
},
{
$group: {
_id: {
year: { $year: "$date" },
month: { $month: "$date" },
day: { $dayOfMonth: "$date" },
hour: { $hour: "$date" }
}
}
}
]);
The SQL query with PostgreSQL is exactly this:
SELECT TO_CHAR(t.date, 'YYYY-MM-DD HH24:00:00') AS italianDate, AVG(value1), AVG(value2) FROM telemetry t WHERE t.system_code = 22 AND t.date BETWEEN '2021-08-01 00:00:00' AND '2021-08-01 02:00:00' GROUP BY italianDate ORDER BY italianDate ASC
Thank you so much if you can help me.

You are actually pretty close. You just need to put the $avg inside your $group stage. Another thing is you need to pay attention to the date formats. Refer to this official MongoDB document for the formats.
db.telemetry.aggregate([
{
$match: {
system_code: 22,
date: {
$gte: ISODate("2021-08-01T00:00:00Z"),
$lte: ISODate("2021-08-01T02:00:00Z")
}
}
},
{
$addFields: {
italianDate: {
$dateToString: {
format: "%Y-%m-%d %H:00:00",
date: "$date"
}
}
}
},
{
$group: {
_id: "$italianDate",
avgValue1: {
$avg: "$value1"
},
avgValue2: {
$avg: "$value2"
}
}
},
{
$sort: {
_id: -1
}
}
])
Here is the Mongo playground for your reference.

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 ISODate but maintain date format

I know I can group data inside a collection by
{
$group: {
_id: {
day: {
$dayOfMonth: '$timeplay'
},
month: {
$month: '$timeplay'
},
year: {
$year: '$timeplay'
}
},
listeners: {
$sum: '$count'
}
}
However, it does not show the date in ISOdate format anymore which is what I need it to still do.
Is there a way to create ISOdate based on this information? As the output currently looks like the following
You can use $dateFromParts operator to convert to date from parts,
{
$group: {
_id: {
$dateFromParts: {
day: { $dayOfMonth: "$timeplay" },
month: { $month: "$timeplay" },
year: { $year: "$timeplay" }
}
},
listeners: { $sum: '$count' }
}
}
Playground
Use this
db.collection.aggregate([
{
$group: {
_id: { $dateTrunc: { date: "$timeplay", unit: "day" } },
listeners: { $sum: '$count' }
}
}
])

MongoDB Aggregate Query, Logins Averages

let pipeline = [{
$match: {
time: { $gt: 980985600 },
user_id: mongoose.Types.ObjectId("60316a2e7641bd0017ced7b1")
}
},
{
$project: {
newDate: { '$toDate': "$time" },
user_id: '$user_id'
}
},
{
$group: {
_id: { week: { $week: "$newDate" }, year: { $year: "$newDate" }},
count: { $sum: 1 }
}
}]
I am currently trying to perform an aggregate through mongoose to find the average logins per week for a specific user. So far I have been able to get to the total number of logins each week, but was curious if there was a way to find the average of these final groupings within the same function. How would I go about doing this?
Just add one last stage to your query:
{
$group: {
_id: null,
avg: { $avg: "$count" }
}
}
So try this:
let pipeline = [
{
$match: {
time: { $gt: 980985600 },
user_id: mongoose.Types.ObjectId("60316a2e7641bd0017ced7b1")
}
},
{
$project: {
newDate: { '$toDate': "$time" },
user_id: '$user_id'
}
},
{
$group: {
_id: { week: { $week: "$newDate" }, year: { $year: "$newDate" } },
count: { $sum: 1 }
}
},
{
$group: {
_id: null,
avg: { $avg: "$count" }
}
}
];

MongoDB aggregate error: expression must have exactly one field

I have the following collection:
{
"_id" : ObjectId("5c3f744a0f05e90001e0085a"),
"date" : ISODate("2016-01-12T12:00:00.779Z"),
}
I need to group my documents by WEEK so I came up with this solution:
db.collection.aggregate([
{
$group : {
_id: { $year: { date: '$date' }, $week: { date: '$date' } },
date: { $first: '$date' }
}
},
{
$sort: { date: -1 }
}
]);
But when I run this I get this error:
An object representing an expression must have exactly one field: { $year: { date: "$date" }, $week: { date: "$date" } }
How can this be fixed or is there any better solution?
Thanks
Problem solved naming the expressions:
db.collection.aggregate([
{
$group : {
_id: {
year: { $year: { date: '$date' } },
week: { $week: { date: '$date' } }
},
date: { $first: '$date' }
}
},
{
$sort: { date: -1 }
}
]);

How to Create Spring Data Aggregate Query

I am new to Spring-Data-Mongo. I have created following query for getting birthday count from each month. Can anyone help me to convert in Spring Data aggreate query.
db.getCollection('users').aggregate(
[
{ $group:
{ _id: { month: { $month: "$birthDate" }, },
count: { $sum:1 },
date: { $first: "$birthDate" }
}
},
{
$project:
{
month:
{
$dateToString: { format: "%m", date: "$date" }
},
count: 1,
_id: 0
}
}])
Here what I have tried so far however it's not giving output what I want.
Aggregation agg = Aggregation.newAggregation(
Aggregation.group("month1").first("birthDate").as("date").count().as("count"),
Aggregation.project("_id").and("date").dateAsFormattedString("%m").as("month").and("count").as("count").andExpression("month(date)").as("month1")