I have the following pipeline in my aggregation:
$group: {
_id: {
$dateToString: {
format: '%Y-%m-%d',
date: '$created_at'
}
},
num: {
$sum: 1
}
}
This returns me the sum of documents grouped by data, as such:
[
{
"_id": "2015-04-21",
"num": 1871
}
]
Now I would like to change the output to something like this:
[
["2015-04-21", 1871]
]
Is this doable within the aggregation pipeline? Or do I have to write my own transformation method?
You can use the $addToSet and $setUnion operators in your pipeline as follows:
db.collection.aggregate([
{
"$group": {
"_id": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$created_at"
}
},
"num": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id",
"A": {
"$addToSet": "$_id"
},
"B": {
"$addToSet": "$num"
}
}
},
{
"$project": {
"_id": 0,
"finalArray": {
"$setUnion": [ "$A", "$B" ]
}
}
}
]);
Output:
/* 0 */
{
"result" : [
{
"finalArray" : ["2015-04-21", 1871]
}
],
"ok" : 1
}
Related
I've got a MongoDB / Nodes aggregation that looks a little like this (there are other values in there, but this is the basic idea).
[
{
'$unwind': {
'path': '$Vehicles'
}
},
{
'$match': {
'Vehicles.Manufacturer': 'FORD'
}
},
{
'$facet': {
'makes': [
{
'$group': {
'_id': '$Vehicles.Manufacturer',
'count': {
'$sum': 1
}
}
}
]
}
},
{
'$project': {
'makes': {
'$sortArray': {
'input': '$makes',
'sortBy': 1
}
}
}
}
]
This works fine. But I would also like to pass an unmatched list through. IE an an array of vehicles whose Manufacturer = FORD and an other list of all Manufacturer.
Can't get it to work. Any ideas please?
Thanks in advance.
Edit:-
The current output looks like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
]
}]
and ideally it would look something like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
],
"unfiltered_makes": [
{
"_id": "ABARTH",
"count": 1
},
{
"_id": "AUDI",
"count": 7
},
{
"_id": "BMW",
"count": 2
},
{
"_id": "CITROEN",
"count": 4
},
{
"_id": "DS",
"count": 1
},
{
"_id": "FIAT",
"count": 1
}.... etc
]
}]
The data looks a bit like this:
"Vehicles": [
{
"Id": 1404908,
"Manufacturer": "MG",
"Model": "3",
"Price": 11995 .... etc
},{
"Id": 1404909,
"Manufacturer": "FORD",
"ManufacturerId": 34,
"Model": "Focus",
"Price": 12000 .... etc
} ... etc
]
In this case you can do something like:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$facet: {
makes: [{$match: {_id: "FORD"}}],
unfiltered_makes: [{$group: {_id: 0, data: {$push: "$$ROOT"}}}]
}
},
{$project: {makes: 1, unfiltered_makes: "$unfiltered_makes.data"}}
])
See how it works on the playground example
Another option is:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$group: {
_id: 0,
unfiltered_makes: {$push: "$$ROOT"},
makes: {$push: {$cond: [{$eq: ["$_id", "FORD"]}, "$$ROOT", "$$REMOVE"]}}
}
}
])
See how it works on the playground example
Here's another way to do it using "$function" to generate a histogram of "Manufacturer" and format the returned array. The javascript function only traverses the "Vehicles" array once, so this may be fairly efficient, although I did not do algorithm timing comparisons on a large collection.
N.B.: I'm a javascript noob and there may be a better way to do this.
db.collection.aggregate([
{
"$set": {
"unfiltered_makes": {
"$function": {
// generate histogram of manufacturers and format output
"body": "function(makes) {const m = new Object();makes.forEach((elem) => {m[elem.Manufacturer] = m[elem.Manufacturer] + 1 || 1});return Object.entries(m).map(([make, count]) => {return {'_id':make, 'count':count}})}",
"args": ["$Vehicles"],
"lang": "js"
}
}
}
},
{
"$project": {
"_id": 0,
"unfiltered_makes": 1,
"makes": {
"$filter": {
"input": "$unfiltered_makes",
"as": "make",
"cond": {
"$eq": [
"$$make._id",
// your search "Manufacturer" goes here
"FORD"
]
}
}
}
}
}
])
Try it on mongoplayground.net.
I have the following query:
[
{
"$group": {
"_id": "$Region",
"Total Sales": {
"$sum": "$Sales"
},
"Average Sales": {
"$avg": "$Sales"
}
}
}
]
This returns the response in the following format:
[
{
"_id": "Canada",
"Total Sales": 66928.17,
"Average Sales": 174.292109375,
}
.....
]
How do I refactor the query to get a response in the following format:
[{
"_id": "Canada",
"Sales":{"Total":66928.17, "Average":174.292109375},
}
......
]
So far I've tried like this but it doesn't work:
{
"$group": {
"_id": "$Region",
"Sales": {
"Total":{
"$sum": "$Sales"
},
"Average":{
"$avg": "$Sales"
}
}
}
}
Use $project to decorate the output document(s).
db.collection.aggregate([
{
"$group": {
"_id": "$Region",
"total": {
"$sum": "$Sales"
},
"average": {
"$avg": "$Sales"
}
}
},
{
$project: {
"Sales": {
"Total": "$total",
"Average": "$average"
}
}
}
])
Sample Mongo PLayground
I Have a bunch of sensordata stored in mongoDB. They are stored like this:
{
"data": [
{
"date": ISODate("2020-02-08T18:06:25.507+00:00"),
"temperature": 20.3,
"humidity": 53.7
},
{
"date": ISODate("2020-02-08T18:07:25.507+00:00"),
"temperature": 21,
"humidity": 54
}
]
}
The day-field is generated by new Date() with JavaScript.
Now i just want the get all the data and convert the "date"-field to a time-field. The result should look like this:
{
"data": [
{
"date": "18:06:25",
"temperature": 20.3,
"humidity": 53.7
},
{
"date": "18:07:25",
"temperature": 21,
"humidity": 54
}
]
}
So is there a way to convert every "date"-field in the array to a "time"-field by using db.collection.aggregate?
I tried using this:
db.collection.aggregate([
{},
{
"$project": {
"data.date": { $dateToString: { format: "%H:%M:%S",date: "$date" } },
"daydata.temperature": 1,
"daydata.humidity": 1
}
}
])
I know it doesnt work, because i dont have any "date"-field outside of "data". But i dont know how to reach the date-field of every data-object and convert it.
You can use $unwind then $project then $group in aggregate.
db.data.aggregate([
{ $unwind: "$data" },
{
$project: {
"data.date": {
$dateToString: { format: "%H:%M:%S", date: "$data.date" },
},
"data.temperature": 1,
"data.humidity": 1,
},
},
{
$group: {
_id: "$_id",
data: { $push: "$data" },
},
},
]);
Also, date should be ISODate
{
"data": [
{
"date": ISODate("2020-02-08T18:06:25.507+00:00"),
"temperature": 20.3,
"humidity": 53.7
},
{
"date": ISODate("2020-02-08T18:07:25.507+00:00"),
"temperature": 21,
"humidity": 54
}
]
}
Since the data.date field is a string, use the sub-string operator to extract the time part of the date field.
db.test.aggregate( [
{
$unwind: "$data"
},
{
$addFields: {
"data.time": { $substrCP: [ "$data.date", 11, 8 ] }
}
},
{
$project: { "data.date": 0 }
},
{
$group: {
_id: "$_id",
data: { $push: "$data" },
// other_fld: { $first: "$other_fld" }
}
}
] ).pretty()
I have a collection similar to this:
[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
I want to aggregate into something like this:
{
"breakfast":[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
}
],
"lunch":[
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
}
I have only managed to group them but I can't change the key meals to either breakfast or lunch depending on the meal.name(group name)
$group: {
_id: { meal: '$meal.name' },
meals: { $push: '$meal' },
}
Using the above code I have managed to produce the output below. My only challenge is changing the key meals to either breakfast or lunch as explained above in the subgroups.
{
"meals":[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
}
],
"meals":[
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
}
Here you can have your answer .
After "grouping" to add to an array you similarly $push all that content into array by the "name" grouping key and then convert into keys of a document in a $replaceRoot with $arrayToObject:
db.collection.aggregate([
{ "$group": {
"_id": "$name",
"data": { "$push": "$$ROOT" }
}},
{ "$group": {
"_id": null,
"data": {
"$push": {
"k": "$_id",
"v": "$data"
}
}
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$data" }
}}
])
OUTPUT
[
{
"breakfast": [
{
"_id": 1,
"name": "breakfast",
"time": "10.00"
},
{
"_id": 2,
"name": "breakfast",
"time": "10.10"
}
],
"lunch": [
{
"_id": 3,
"name": "lunch",
"time": "12.07"
},
{
"_id": 4,
"name": "lunch",
"time": "12.45"
}
]
}
]
You can check the result of above query in this LINK
I have appointment collection in that i have status codes like upcoming, cancelled, completed. i want to write an api to get count of each status using mongoose or mongodb methods.
output should be like below
[{
group : "grp1",
appointments_completed :4
appointments_upcoming :5
appointments_cancelled : 7
}]
thanks in advance.
I hope it help you
db.getCollection('codelist').aggregate([
{
$group:{
_id:{status:"$status"},
count:{$sum:1}
}
}
])
The result will be
[{
"_id" : {
"status" : "cancelled"
},
"count" : 13.0
},
{
"_id" : {
"status" : "completed"
},
"count" : 20.0
}
]
I think you can process it with nodejs
Using Aggregation Pipeline $group we can get this count
db.collection_name.aggregate([
{ $group: {
_id:null,
appointments_completed: {$sum : "$appointments_completed" },
appointments_upcoming:{$sum :"$appointments_upcoming"},
appointments_cancelled:{$sum: "$appointments_cancelled"}
}
}
]);
With MongoDb 3.6 and newer, you can leverage the use of $arrayToObject operator and a $replaceRoot pipeline to get the desired result. You would need to run the following aggregate pipeline:
db.appointments.aggregate([
{ "$group": {
"_id": {
"group": <group_by_field>,
"status": { "$concat": ["appointments_", { "$toLower": "$status" }] }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.group",
"counts": {
"$push": {
"k": "$_id.status",
"v": "$count"
}
}
} },
{ "$addFields": {
"counts": {
"$setUnion": [
"$counts", [
{
"k": "group",
"v": "$_id"
}
]
]
}
} },
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$counts" }
} }
])
For older versions, a more generic approach though with a different output format would be to group twice and get the counts as an array of key value objects as in the following:
db.appointments.aggregate([
{ "$group": {
"_id": {
"group": <group_by_field>,
"status": { "$toLower": "$status" }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.group",
"counts": {
"$push": {
"status": "$_id.status",
"count": "$count"
}
}
} }
])
which spits out:
{
"_id": "grp1"
"counts":[
{ "status": "completed", "count": 4 },
{ "status": "upcoming", "count": 5 }
{ "status": "cancelled", "count": 7 }
]
}
If the status codes are fixed then the $cond operator in the $group pipeline step can be used effectively to evaluate the counts based on the status field value. Your overall aggregation pipeline can be constructed as follows to produce the result in the desired format:
db.appointments.aggregate([
{ "$group": {
"_id": <group_by_field>,
"appointments_completed": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "completed" ] }, 1, 0 ]
}
},
"appointments_upcoming": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "upcoming" ] }, 1, 0 ]
}
},
"appointments_cancelled": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "cancelled" ] }, 1, 0 ]
}
}
} }
])