Nested output from mongo aggregate query - mongodb

This is right out of the mongo aggregation documentation. Lets say I have these set of documents:
{ _id: 1, cust_id: "abc1", ord_date: ISODate("2012-11-02T17:04:11.102Z"), status: "A", amount: 50 }
{ _id: 2, cust_id: "xyz1", ord_date: ISODate("2013-10-01T17:04:11.102Z"), status: "A", amount: 100 }
{ _id: 3, cust_id: "xyz1", ord_date: ISODate("2013-10-12T17:04:11.102Z"), status: "D", amount: 25 }
{ _id: 4, cust_id: "xyz1", ord_date: ISODate("2013-10-11T17:04:11.102Z"), status: "D", amount: 125 }
{ _id: 5, cust_id: "abc1", ord_date: ISODate("2013-11-12T17:04:11.102Z"), status: "A", amount: 25 }
I can run this aggregate query:
db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
])
To get this response:
{ "_id" : "xyz1", "total" : 100 }
{ "_id" : "abc1", "total" : 75 }
But what if a want the response in a nested format? Any way of achieving that without using mapReduce? Something like this:
{ "_id" : "xyz1", "amount": { "total" : 100 } }
{ "_id" : "abc1", "amount": { "total" : 75 } }

You need to project your documents using the $project operator
db.collection.aggregate([
{ "$group": {
"_id": "$cust_id",
"total": { "$sum": "$amount" }
}},
{ "$project": { "amount.total": "$total" } },
{ "$sort": { "amount.total": -1 } }
])
Which returns:
{ "_id" : "xyz1", "amount" : { "total" : 250 } }
{ "_id" : "abc1", "amount" : { "total" : 75 } }

Related

How to write one query (count distinct, sum) in MongoDB?

Query: select count(distinct finish_date), sum(study_num) from table where student_id=1234
Documents:
{
"_id" : ObjectId("602252684a43d5b364f3e6ca"),
"student_id" : 1234,
"study_num" : 8,
"finish_date" : "20210209",
},
{
"_id" : ObjectId("602257594a43d5b364f4cc6a"),
"student_id" : 1234,
"study_num" : 7,
"finish_date" : "20210207",
},
{
"_id" : ObjectId("5fbb65580d685b17fa56e18f"),
"student_id" : 2247,
"study_num" : 6,
"finish_date" : "20210209",
}
You can use $match and $group
db.collection.aggregate([
{
"$match": {"student_id": 1234}
},
{
"$group": {
"_id": "$finish_date",
"study_sum": { $sum: "$study_num" }
}
},
{
"$group": {
"_id": null,
"study_sum": { $sum: "$study_sum" },
count: { $sum: 1 }
}
}
])
Working Mongo playground
Query: select count(distinct finish_date), sum(study_num) from table
where student_id=1234
How to write the query? Write using an aggregation:
db.collection.aggregate([
{
$match: { student_id: 1234 }
},
{
$group: {
_id: "",
distinct_dates: { $addToSet: "$finish_date" },
study_sum: { $sum: "$study_num" }
}
},
{
$project: {
count: { $size: "$distinct_dates" },
study_sum: 1, _id: 0
}
}
])
The output: { "study_sum" : 15, "count" : 2 }
Reference: SQL to Aggregation Mapping Chart

Multilevel $group using mongodb

I am trying to get the count of all the different value of a key in my MongoDB. I am getting the count as well but i am getting it with 2 different objects.
{ "_id" : ObjectId("596f6e95b6a1aa8d363befeb"), produce:"potato","variety" : "abc", "state" : 'PA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befec"), produce:"potato", "variety" : "abc", "state" : 'PA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befed"), produce:"potato", "variety" : "def", "state" : 'IA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befee"), produce:"potato", "variety" : "def", "state" : 'IA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befef"), produce:"potato", "variety" : "abc", "state" : 'DA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befeg"), produce:"potato", "variety" : "abc", "state" : 'DA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befeh"), produce:"potato", "variety" : "def", "state" : 'DA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befei"), produce:"potato", "variety" : "abc", "state" : 'IA' }
db.aggregate([
{
$match:{produce: "potato"}
},
{
"$group":{
"_id":{"variety":"$variety","state":"$state"},
"count":{"$sum":1}
}
},
{
"$group":{
"_id":null,
"counts":{
"$push": {"filterkey":"$_id.variety","state":"$_id.state","count":"$count"}
}
}
},
])
Actual Result : -
counts
[
{ filterkey: 'abc', state: 'PA', count: 2},
{ filterkey: 'abc', state: 'IA', count: 1},
{ filterkey: 'abc', state: 'DA', count: 2},
{ filterkey: 'def', state: 'IA', count: 2},
{ filterkey: 'def', state: 'DA', count: 1}
]
Expected Result : -
counts
[
{ filterkey: 'abc', states:{'PA':2,'IA':1,'DA':2},
{ filterkey: 'def', states:{'IA':2,'DA':1}
]
Is there is some way to get the data like this?
You need to use multilevel $group ing here. First you need to use $group with the variety and state fields and need to $sum to get total number of unique document per variety and state.
Then second you need to use $group with the variety to get the number of unique documents per variety.
And Finally $arrayToObject to flatten the states array.
db.collection.aggregate([
{ "$match": { "produce": "potato" }},
{ "$group": {
"_id": { "variety": "$variety", "state": "$state" },
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.variety",
"states": {
"$push": {
"k": "$_id.state",
"v": "$count"
}
}
}},
{ "$addFields": {
"states": {
"$arrayToObject": "$states"
}
}}
])
You can remove stages one by one here and can see what actually happens.
Output
[
{
"_id": "def",
"states": {
"DA": 1,
"IA": 2
}
},
{
"_id": "abc",
"states": {
"DA": 2,
"IA": 1,
"PA": 2
}
}
]

Changing format of mongoDB aggregation result [duplicate]

This question already has an answer here:
Promote subfields to top level in projection without listing all keys
(1 answer)
Closed 4 years ago.
Currently I am running this query to calculate averages and to return the data in a specific format:
db.metrics.aggregate([
{
$unwind:"$data"
},
{
$group:{
_id:"$data.configName",
avg:{
$avg:"$data.linesCount"
},
data:{
$last:"$data"
},
date:{
$last:"$date"
}
}
}
]).pretty()
On a collection which contains objects in this format:
{
"_id" : {
"date" : 1526569274000,
}
"date" : "20150220",
"data" : [
{
"configName" : "aaa",
"linesCount" : 500,
"insertedLinesCount" : 658,
}
],
"applicationName" : "loader"
}
Which returns this result:
{
"_id" : "aaa",
"avg" : 500,
"data" : {
"configName" : "aaa",
"linesCount" : 500,
"insertedLinesCount" : 658,
"succeeded" : true
},
"date" : "20150220"
}
The details are correct but I'd like to change the format. Is there any way to take what is in the data object and return it so that the final result is a list of 1-1 mappings, like so:
{
"_id" : "aaa",
"avg" : 500,
"configName" : "aaa",
"linesCount" : 500,
"insertedLinesCount" : 658,
"fileFormat" : "",
"date" : "20150220"
}
You need to use the $project stage at the end of the result
db.collection.aggregate([
{
$unwind: "$data"
},
{
$group: {
_id: "$data.configName",
avg: {
$avg: "$data.linesCount"
},
data: {
$last: "$data"
},
date: {
$last: "$date"
}
}
},
{
$project: {
configName: "$data.configName",
insertedLinesCount: "$data.insertedLinesCount",
linesCount: "$data.linesCount",
succeeded: "$data.succeeded",
_id: 1,
avg: 1,
date: 1
}
}
])
above query gives you the following result... check it here
[
{
"_id": "aaa",
"avg": 500,
"configName": "aaa",
"date": "20150220",
"insertedLinesCount": 658,
"linesCount": 500,
"succeeded": true
}
]
Add a $replaceRoot stage after $group
{
$replaceRoot: {
newRoot: {
_id: "$_id",
avg: "$avg",
configName: "$data.configName"
...
}
}
}

Get data for month wise in mongodb

I want to get data to each month. in my table data is stored like this:-
"patient" : [
{
"status" : 'arrived',
start_time: '2017-08-17T09:17:00.000Z
},
{
"status" : 'arraived',
start_time: '2017-08-16T07:17:00.000Z
},
{
"status" : 'arrived',
start_time: '2017-07-12T09:17:00.000Z
},
{
"status" : 'arraived',
start_time: '2017-07-05T08:10:00.000Z
},
{
"status" : 'arrived',
start_time: '2017-06-02T09:17:00.000Z
},
{
"status" : 'arraived',
start_time: '2017-05-05T08:16:00.000Z
}
]
etc,
and I want to sum of patient of each month (jan to des), like this :-
{
"month" : 8,
"count" : 2
}and like this month 1 to 12
I assume, patient array is associated with a customer and the date is stored in mongo ISO format.
So, the actual document would look like :
{
name: "stackOverflow",
"patient" : [
{
"status" : 'arrived',
"start_time": ISODate("2017-08-17T09:17:00.000Z")
},
{
"status" : 'arraived',
"start_time": ISODate("2017-08-16T07:17:00.000Z")
},
{
"status" : 'arrived',
"start_time": ISODate("2017-07-12T09:17:00.000Z")
},
{
"status" : 'arraived',
"start_time": ISODate("2017-07-05T08:10:00.000Z")
},
{
"status" : 'arrived',
"start_time": ISODate("2017-06-02T09:17:00.000Z")
},
{
"status" : 'arraived',
"start_time": ISODate("2017-05-05T08:16:00.000Z")
}
]
}
here is a sample query which you can try -
db.test.aggregate([
{$unwind: "$patient"},
{ $group: {
_id: {name: "$name", month: {$month: "$patient.start_time"}},
count: { $sum: 1}
}},
{$group: {
_id: "$_id.name",
patient: {$push: {month: "$_id.month", count: "$count"}}
}}
])
Sample output:
{
"_id" : "stackOverflow",
"patient" : [
{
"month" : 5,
"count" : 1
},
{
"month" : 6,
"count" : 1
},
{
"month" : 7,
"count" : 2
},
{
"month" : 8,
"count" : 2
}
]
}
You can change query according to your use-case. hope this will help you!
This is my code:-
db.appointments.aggregate( [
{
$project:
{
"patient_id": 1,
"start_time": 1,
"status": 1
}
},
{
$match: {
'start_time' : { $gte: startdate.toISOString() },
'status': { $eq: 'arrived' }
} ,
},
{ $group: {
_id: {id: "$_id", start_time: {$month: "$appointments.start_time"}},
count: { $sum: 1}
}}
])
When I used this :-
{ $group: {
_id: {id: "$_id", start_time: {$month: "$start_time"}},
count: { $sum: 1}
}
}
its showing error message:-
{"name":"MongoError","message":"can't convert from BSON type missing to Date","ok":0,"errmsg":"can't convert from BSON type missing to Date","code":16006,"codeName":"Location16006"}
And when I comment this its showing this :-
Out Put here:-
:[{"count":{"_id":"595b6f95ab43ec1f6c92b898","patient_id":"595649904dbe9525c0e036ef","start_time":"2017-07-04T10:35:00.000Z","status":"arrived"}},
{"count":{"_id":"595dff870960d425d4f14633","patient_id":"5956478b4dbe9525c0e036ea","start_time":"2017-03-08T09:14:00.000Z","status":"arrived"}},{"count":{"_id":"595dffaa0960d425d4f14634","patient_id":"595649904dbe9525c0e036ef","start_time":"2017-03-17T09:15:00.000Z","status":"arrived"}},{"count":{"_id":"595dffcf0960d425d4f14635","patient_id":"595648394dbe9525c0e036ec","start_time":"2017-06-08T09:15:00.000Z","status":"arrived"}},{"count":{"_id":"595dfffb0960d425d4f14636","patient_id":"5956478b4dbe9525c0e036ea","start_time":"2017-06-20T09:16:00.000Z","status":"arrived"}},{"count":{"_id":"595e00160960d425d4f14637","patient_id":"5959ea7f80388b19e0b57817","start_time":"2017-08-17T09:17:00.000Z","status":"arrived"}}]}
const group = {
$group: {
_id: { month: { $month: "$createdAt" } },
count: { $sum: 1 },
},
};
const groups = {
$group: {
_id: null,
patient: { $push: { month: '$_id.month', count: '$count' } },
},
};
return db.Patient.aggregate([group, groups]);

Aggregate Fields together

I have the following structure as an input from which data needs to be aggregated:
I need to aggregate the data such that I end up with the following structure:
start: A {
tripdetails: [{
destination: B [{
duration: 10,
type: male
},
duration: 12,
type: female
},
duration: 9,
type: female
}]
]}
}
Basically I need to group "type" and "duration" together under the same destination.
I came up with the following query, but this results in a a single field for "type" for each "destination", but not for every "duration".
db.test.aggregate(
{
$group: {
_id: {"StationID": "$start", "EndStationID": "$destination"},
durations: {$addToSet: "$duration" },
usertypes: {$addToSet: "$type" }
}
},
{
$group: {
_id: "$_id.StationID",
Tripcount_out: {$sum: "durations"},
Trips_out: { $addToSet: { EndStationID: "$_id.EndStationID", Tripduration: "$durations", Usertype: "$usertypes"} }
}
}
)
My question is how I can achieve the structure described above.
You could try running the following aggregate pipeline:
db.test.aggregate([
{
"$group": {
"_id": { "StationID": "$start", "EndStationID": "$destination" },
"details": {
"$push": {
"duration": "$duration",
"type": "$type"
}
}
}
},
{
"$group": {
"_id": "$_id.StationID",
"tripdetails": {
"$push": {
"destination": "$_id.EndStationID",
"trips": "$details"
}
}
}
}
])
which yields:
{
"_id" : "A",
"tripdetails" : [
{
"destination" : "B",
"trips" : [
{
"duration" : 10,
"type" : "male"
},
{
"duration" : 9,
"type" : "female"
},
{
"duration" : 12,
"type" : "female"
}
]
}
]
}