Mongo Aggregate $project variable as field name - mongodb

Mongo 4.2
Have a bunch of data for server instance and CPU usage.
{
"_id" : "1",
"instance" : "172.00.00.01",
"client" : "A",
"date" : ISODate("2020-12-06T22:47:00.163Z"),
"app" : 0.0133317
}
{
"_id" : "2",
"instance" : "172.00.00.01",
"client" : "A",
"date" : ISODate("2020-12-06T22:47:03.163Z"),
"app" : 0.0400000
}
{
"_id" : "3",
"instance" : "172.00.00.02",
"client" : "B",
"date" : ISODate("2020-12-06T22:47:00.163Z"),
"app" : 0.0800000
}
I am wanting this as my result, data grouped per minute and then fields for each instance with MAX "app" value.
{
"_id" : {
"minute" : 47,
"hour" : 22,
"day" : 6,
"month" : 12,
"year" : 2020
},
"172.00.00.01": 0.0400000,
"172.00.00.02": 0.0800000
}
So my aggregate needs to group to get minutes:
{
"$group": {
"_id": {
"minute": {
"$minute": {
"date": "$date",
"timezone": "America/Chicago"
}
},
"hour": {
"$hour": {
"date": "$date",
"timezone": "America/Chicago"
}
},
"day": {
"$dayOfMonth": {
"date": "$date",
"timezone": "America/Chicago"
}
},
"month": {
"$month": {
"date": "$date",
"timezone": "America/Chicago"
}
},
"year": {
"$year": {
"date": "$date",
"timezone": "America/Chicago"
}
},
instance: '$instance',
client: '$client'
},
app: {
$max: '$app'
}
}
}
That pipeline gives me this as result:
{
"_id" : {
"minute" : 47,
"hour" : 22,
"day" : 6,
"month" : 12,
"year" : 2020,
"instance" : "172.00.00.01",
"client" : "A"
},
"app" : 0.040000
},
{
"_id" : {
"minute" : 47,
"hour" : 22,
"day" : 6,
"month" : 12,
"year" : 2020,
"instance" : "172.00.00.02",
"client" : "B"
},
"app" : 0.080000
}
So to get to the desired output, I understand I need to group again without instance/client to combine the two, but how do I dynamically get the instance variable as field name? These do not work!
{
"$addFields": {
"[$instance]": "$app"
}
},
{
"$addFields": {
"[$$instance]": "$app"
}
},
{
"$addFields": {
"$instance": "$app"
}
},
{
"$addFields": {
"$$instance": "$app"
}
},

With your code, additionally
{
$group: {
_id: {
day: "$_id.day",
hour: "$_id.hour",
minute: "$_id.minute",
month: "$_id.month",
"year": "$_id.year"
},
data: {
$push: { k: "$_id.instance", v: "$app" }
}
}
},
{
$addFields: {
data: { "$arrayToObject": "$data" }
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ "$data", "$$ROOT" ]
}
}
},
{
$project: {
data: 0
}
}
Working Mongo playground

Related

In MongoDB Shell, How to query for day-by-day data for a time period (such as a month)

I want to get day wise data grouped by class (field from my collection) for a month of period.
This is what i have tried to query, but doesn't give the expected output.
query:
db.collection.aggregate([
// Get only records created in the last 30 days
{$match:{
"created_at":{$gt: new Date(ISODate().getTime() - 1000*60*60*24*30)},
}},
// Get the year, month and day from the created_at TimeStamp
{$project:{
"year":{$year:"$created_at"},
"month":{$month:"$created_at"},
"day": {$dayOfMonth:"$created_at"}
}},
// Group by year, month and day and get the count
{$group:{
_id:{year:"$year", month:"$month", day:"$day"},
"count":{$sum:1}
}},
// Group by class field
{ $group: {
_id: "$meta.class",
total: {$sum: 1}
}}
])
expected output:
{ "_id" : { "year" : 2021, "month" : 10, "day" : 01 }, "count" : {{ "_id" : "class1", "total" : 15 }, { "_id" : "class2", "total" : 25 }} }
{ "_id" : { "year" : 2021, "month" : 10, "day" : 02 }, "count" : {{ "_id" : "class2", "total" : 25 }, { "_id" : "class3", "total" : 10 }} }
...
{ "_id" : { "year" : 2021, "month" : 10, "day" : 30 }, "count" : {{ "_id" : "class3", "total" : 50 }} }
So, could someone please tell me how to attain the desired result or what I'm doing wrong?
Thanks.
---EDIT---
Sample Data:
mongoplayground link : here
[
{
"key": 1,
"_id": ObjectId("615602280000000000000000"),
"created_at": ISODate("2021-09-30T18:30:00.000Z"),
"meta": {
"class": "class1",
}
},
{
"key": 2,
"_id": ObjectId("615753a80000000000000000"),
"created_at": ISODate("2021-10-01T18:30:00.000Z"),
"meta": {
"class": "class1",
}
},
{
"key": 3,
"_id": ObjectId("615764100000000000000000"),
"created_at": ISODate("2021-10-01T19:40:00.000Z"),
"meta": {
"class": "class1",
}
},
{
"key": 4,
"_id": ObjectId("615776d00000000000000000"),
"created_at": ISODate("2021-10-01T21:00:00.000Z"),
"meta": {
"class": "class2",
}
}
]
sample o/p:
[
{
"_id":{
"year":2021,
"month":10,
"day":1
},
"count":[
{
"_id":"class1",
"total":1
}
]
},
{
"_id":{
"year":2021,
"month":10,
"day":2
},
"count":[
{
"_id":"class1",
"total":2
},
{
"_id":"class2",
"total":1
}
]
}
]
Try this one:
db.collection.aggregate([
{ $match: { created_at: { $gt: moment().startOf('day').subtract(30, 'day').toDate() } } },
{
$group: {
_id: {
day: { $dateTrunc: { date: "$created_at", unit: "day" } },
class: "$meta.class"
},
total: { $count: {} }
}
},
{
$group: {
_id: "$_id.day",
count: { $push: { total: "$total", class: "$_id.class" } }
}
}
])
Whenever I have to work with date/time values then I suggest to use the moment.js library.
$dateTrunc() requires MongoDB version 5.0. If you prefer or require year/month/day parts it should be fairly obvious how to modify the aggregation pipeline.

How to regroup (or merge) objects in aggregation pipeline in MongoDB?

I currently have an aggregation pipeline:
db.getCollection('forms').aggregate([
{ $unwind: //unwind },
{
$match: {
//some matches
}
},
{
$project: {
//some projections
}
},
{
//Finally, im grouping the results
$group: {
_id: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
raceEthnicity: '$demographic.raceEthnicity'
},
count: { $sum: 1 },
}
]
My current results are similar to:
[{
"_id" : {
"year" : 2020,
"month" : 11,
"raceEthnicity" : "Asian"
},
"count" : 1.0
},
{
"_id" : {
"year" : 2020,
"month" : 11,
"raceEthnicity" : "Multiracial"
},
"count" : 3.0
},
{
"_id" : {
"year" : 2020,
"month" : 11,
"raceEthnicity" : "White"
},
"count" : 3.0
},
{
"_id" : {
"year" : 2020,
"month" : 10,
"raceEthnicity" : "White"
},
"count" : 33.0
}]
Is there a way to add a new stage on the pipeline to "merge" results of the same year/month into a single object?
I want to achieve something like:
{
"_id" : {
"year" : 2020,
"month" : 11,
},
"Asian" : 1.0,
"Multiracial": 3.0,
"White": 1.0
},
{
"_id" : {
"year" : 2020,
"month" : 10,
},
"White": 33
}
Is it possible? How can I do that?
Add this one to your aggregation pipeline.
db.collection.aggregate([
{ $set: { "data": { k: "$_id.raceEthnicity", v: "$count" } } },
{ $group: { _id: { year: "$_id.year", month: "$_id.month" }, data: { $push: "$data" } } },
{ $set: { "data": { $arrayToObject: "$data" } } },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$$ROOT", "$data"] } } },
{ $unset: "data" }
])
Unlike the solution from #wak786 you don't need to know all ethnicity at design time. It works for arbitrary ethnicity.
Add these stages to your pipeline.
db.collection.aggregate([
{
$replaceRoot: {
newRoot: {
"$mergeObjects": [
"$$ROOT",
{
$arrayToObject: [
[
{
k: "$_id.raceEthnicity",
v: "$count"
}
]
]
}
]
}
}
},
{
"$group": {
"_id": {
year: "$_id.year",
month: "$_id.month",
},
"Asian": {
"$sum": "$Asian"
},
"Multiracial": {
"$sum": "$Multiracial"
},
"White": {
"$sum": "$White"
}
}
}
])
Below is the mongo playground link. I have taken the current result of your pipeline as input to my query.
Try it here

MongoDB nested aggregation based on time values

I have a structure like this coming from IOT sensor device
[{
"_id" : ObjectId("5e8e1af3f1563046e084cf65"),
"value" : 462.2382850719,
"start" : 1586293200001,
"year" : 2020,
"month" : "04",
"day" : "07",
"hour" : "21",
"channelId" : 3462
},
{
"_id" : ObjectId("5e8e1af3f1563046e084cf64"),
"value" : 1636.8770905333,
"start" : 1586289600001,
"year" : 2020,
"month" : "04",
"day" : "07",
"hour" : "19",
"channelId" : 3462
},
{
"_id" : ObjectId("5e8e1af3f1563046e084cf63"),
"value" : 1665.4116577475,
"start" : 1586286000001,
"year" : 2020,
"month" : "04",
"day" : "07",
"hour" : "20",
"channelId" : 3462
}]
I want to group this structure first by channelId, then by year,month,day and hour. I want to develop this sort of nested structure
{"channel_id":XXX,"aggregates":[2020:[04:[01:[00:AVG_VALUE,01:AVG_VALUE...],...],...]}
Similar like this
output = [
{
channelId: 3462,
"value": '',
aggregates: [
{
year: 2020,
months: [
{
month: 4,
value: '',
days: [
{
day: 7,
value: '',
hours: [
{ hour: 19, value: '' },
{ hour: 20, value: '' },
{ hour: 21, value: '' }
]
}
]
}
]
}
]
}
]
I can do one level $group by and then push the $$ROOT in it, but don't know how to go nested with groups. Do I have to use reduce logic or any.
Need help
Check if the solution meets your requirements:
db.collection.aggregate([
{
$group: {
_id: {
"channelId": "$channelId",
"year": "$year",
"month": "$month",
"day": "$day",
"hour": "$hour"
},
value: {
$avg: "$value"
}
}
},
{
$group: {
_id: {
"channelId": "$_id.channelId",
"year": "$_id.year",
"month": "$_id.month",
"day": "$_id.day"
},
value: {
$avg: "$value"
},
hours: {
$push: {
hour: "$_id.hour",
value: "$value"
}
}
}
},
{
$group: {
_id: {
"channelId": "$_id.channelId",
"year": "$_id.year",
"month": "$_id.month"
},
value: {
$avg: "$value"
},
days: {
$push: {
day: "$_id.day",
value: "$value",
hours: "$hours"
}
}
}
},
{
$group: {
_id: {
"channelId": "$_id.channelId",
"year": "$_id.year"
},
value: {
$avg: "$value"
},
months: {
$push: {
month: "$_id.month",
value: "$value",
days: "$days"
}
}
}
},
{
$group: {
_id: {
"channelId": "$_id.channelId"
},
channelId: {
$first: "$_id.channelId"
},
value: {
$avg: "$value"
},
aggregates: {
$push: {
year: "$_id.year",
value: "$value",
months: "$months"
}
}
}
},
{
$project: {
_id: 0
}
}
])
MongoPlayground

mongo aggregation framework group by quarter/half year/year

I have a database with this schema structure :
{
"name" : "Carl",
"city" : "paris",
"time" : "1-2018",
"notes" : [
"A",
"A",
"B",
"C",
"D"
]
}
And this query using the aggregation framework :
db.getCollection('collection').aggregate(
[{
"$match": {
"$and": [{
"$or": [ {
"time": "1-2018"
}, {
"time": "2-2018"
} ]
}, {
"name": "Carl"
}, {
"city": "paris"
}]
}
}, {
"$unwind": "$notes"
}, {
"$group": {
"_id": {
"notes": "$notes",
"time": "$time"
},
"count": {
"$sum": 1
}
}
}
, {
"$group": {
"_id": "$_id.time",
"count": {
"$sum": 1
}
}
}, {
"$project": {
"_id": 0,
"time": "$_id",
"count": 1
}
}])
It working correcly and i'm getting these results these results :
{
"count" : 4.0,
"time" : "2-2018"
}
{
"count" : 4.0,
"time" : "1-2018"
}
My issue is that i'd like to keep the same match stage and i'd like to group by quarter.
Here the result i'd like to have :
{
"count" : 8.0,
"time" : "1-2018" // here quarter 1
}
Thanks

Mongodb aggregate every two hours

I have an aggregate query of the following form
db.mycollection.aggregate([
{
"$match":
{
"Time": { $gte: ISODate("2016-01-30T00:00:00.000+0000") }
}
},
{
"$group":
{
"_id":
{
"day": { "$dayOfYear": "$Time" },
"hour": { "$hour": "$Time" }
},
"Dishes": { "$addToSet": "$Dish" }
}
},
{
"$group":
{
"_id": "$_id.hour",
"Food":
{
"$push":
{
"Day": "$_id.day",
"NumberOfDishes": { "$size":"$Dishes" }
}
}
}
},
{
"$project":
{
"Hour": "$_id",
"Food": "$Food",
"_id" : 0
}
},
{
"$sort": { "Hour": 1 }
}
]);
Instead of doing this as above in one hour durations e.g. 0-1,1-2,2-3,3-4,4-5,...,23-24, I want to be able to do this in two hour durations. e.g. 0-2,2-4,4-6,...,22-24. Is there a way to do that?
Hint: use arithmetic aggregation operators in $project
Lets say, H=floor(hour/2), where hour is actual hour from document date. Then you can get H by applying $floor and $divide operators to this date
"H": { $floor: { $divide: [ { "$hour": "$Time" }, 2 ] } }
Here H corresponds to the pair of hours (Hours=[0,2) => H=0, Hours=[2,4) => H=1, Hours=[22,24) => H=11, etc.) and you can pass it to the $group stage with
$group: { "_id": { "day": { $dayOfYear: "$Time" }, "H": "$H" } }
Then you can output the pair of hours for specific H with
"Hours": [ { $multiply: [ "$H", 2 ] }, { $sum: [ { $multiply: [ "$H", 2 ] }, 2 ] } ]
Given collection of documents
{ "Time" : ISODate("2016-01-30T01:00:00Z"), "Dish" : "dish1" }
{ "Time" : ISODate("2016-01-30T02:00:00Z"), "Dish" : "dish2" }
{ "Time" : ISODate("2016-01-30T03:00:00Z"), "Dish" : "dish3" }
{ "Time" : ISODate("2016-01-30T04:00:00Z"), "Dish" : "dish4" }
{ "Time" : ISODate("2016-01-30T05:00:00Z"), "Dish" : "dish5" }
{ "Time" : ISODate("2016-01-30T06:00:00Z"), "Dish" : "dish6" }
{ "Time" : ISODate("2016-01-30T07:00:00Z"), "Dish" : "dish7" }
{ "Time" : ISODate("2016-01-30T08:00:00Z"), "Dish" : "dish8" }
{ "Time" : ISODate("2016-01-30T09:00:00Z"), "Dish" : "dish9" }
and using the next aggregate on it
db.mycollection.aggregate([
{
"$match":
{
"Time": { $gte: ISODate("2016-01-30T00:00:00.000+0000") }
}
},
{
"$project":
{
"Dish": 1,
"Time": 1,
"H": { $floor: { $divide: [ { $hour: "$Time" }, 2 ] } }
}
},
{
"$group":
{
"_id":
{
"day": { $dayOfYear: "$Time" },
"H": "$H"
},
"Dishes": { $addToSet: "$Dish" }
}
},
{
"$group":
{
"_id": "$_id.H",
"Food":
{
"$push":
{
"Day": "$_id.day",
"NumberOfDishes": { $size: "$Dishes" }
}
}
}
},
{
"$sort": { "_id": 1 }
},
{
"$project":
{
"Hours": [ { $multiply: [ "$_id", 2 ] }, { $sum: [ { $multiply: [ "$_id", 2 ] }, 2 ] } ],
"Food": "$Food",
"_id": 0
}
}
]);
provides the result
{ "Food" : [ { "Day" : 30, "NumberOfDishes" : 1 } ], "Hours" : [ 0, 2 ] }
{ "Food" : [ { "Day" : 30, "NumberOfDishes" : 2 } ], "Hours" : [ 2, 4 ] }
{ "Food" : [ { "Day" : 30, "NumberOfDishes" : 2 } ], "Hours" : [ 4, 6 ] }
{ "Food" : [ { "Day" : 30, "NumberOfDishes" : 2 } ], "Hours" : [ 6, 8 ] }
{ "Food" : [ { "Day" : 30, "NumberOfDishes" : 2 } ], "Hours" : [ 8, 10 ] }