How to total Count in mongoDB? - node.js

Could you please tell me how to count total person which have status p.
Here is my code
https://mongoplayground.net/p/d2Bmk4srq0O
db.collection.aggregate({
$group: {
_id: "$Department",
totalAttendance: {
$sum: "$Status"
},
}
})
I want to count all document which has status p
expected output . something like that
[
{
"_id": "THBS",
"totalAttendance": 10
},
{
"_id": "HUAWEI",
"totalAttendance": 2
}
]

You need put $sum $conditionally here
db.collection.aggregate([
{ "$group": {
"_id": "$Department",
"totalAttendance": {
"$sum": {
"$cond": [
{ "$eq": [{ "$ltrim": { "input": "$Status" } }, "P "] },
1,
0
]
}
}
}}
])
MongoPlayground

Related

MongoDB - How do I add multiple aggregations in query

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

Is it a good idea writing multiple aggregate in mongodb?

My schema looks like this
{
_id: ObjectID,
gender: "MALE", // MALE or FEMALE
status: "ACTIVE", // ACTIVE or INACTIVE
method: "A" // A or B
}
The API needs to return a total document count, total count by gender, total count by status and total count by method. My current approach is making multiple aggregate calls and one count method.
As such,
const genderCursor = db.collection(Collection.Sample).aggregate([
{"$group": { _id: "$gender", count: { $sum: 1 }}}
]);
const statusCursor = db.collection(Collection.Sample).aggregate([
{"$group": { _id: "$status", count: { $sum: 1 }}}
]);
const methodCursor = db.collection(Collection.Sample).aggregate([
{"$group": { _id: "$method", count: { $sum: 1 }}}
]);
const total = await db.collection(Collection.Sample).count();
await genderCursor.forEach(x => gender.push({ name: x._id, count: x.count}))
await statusCursor.forEach(x => statuses.push({ name: x._id, count: x.count}))
await methodCursor.forEach(x => methods.push({ name: x._id, count: x.count}))
Results,
{
"total": 100,
"gender": [
{
"name": "MALE",
"count": 30
},
{
"name": "FEMALE",
"count": 70
},
],
"statuses": [
{
"name": "APPROVED",
"count": 81
},
{
"name": "CREATED",
"count": 19
},
],
"methods": [
{
"name": "A",
"count": 50
},
{
"name": "B",
"count": 50
},
],
}
Is there a better and cost effective method to achieve the same thing as above?
You should combine all the queries into a single Aggregation Query since it will reduce your network roundtrip times and load on MongoDB servers.
There are two methods in doing this.
Method-1: Using null Group
You can group with _id null and apply $cond Operator. This is much
faster than the second method, but you have to apply all the outcomes required in the $cond.
Choose whichever method works best for your use case.
db.collection.aggregate([
{
"$group": {
"_id": null,
"male": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$gender",
"MALE"
]
},
"then": 1,
"else": 0,
},
},
},
"female": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$gender",
"FEMALE"
]
},
"then": 1,
"else": 0,
},
}
},
"active": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$status",
"ACTIVE"
]
},
"then": 1,
"else": 0,
},
}
},
"inactive": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$status",
"INACTIVE"
]
},
"then": 1,
"else": 0,
}
},
},
"methodA": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$method",
"A"
]
},
"then": 1,
"else": 0,
},
}
},
"methodB": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$method",
"B"
]
},
"then": 1,
"else": 0,
},
},
}
}
},
])
Mongo Playground Sample Execution
Method-2: Using $facet
You can also use the $facet stage, but it requires more computation on MongoDB compared with $group, but you don't have to write all the outcomes manually.
db.collection.aggregate([
{
"$facet": {
"gender": [
{
"$group": {
"_id": "$gender",
"count": {
"$sum": 1
}
}
},
],
"status": [
{
"$group": {
"_id": "$status",
"count": {
"$sum": 1
}
}
},
],
"method": [
{
"$group": {
"_id": "$method",
"count": {
"$sum": 1
}
}
},
],
}
}
])
Mongo Playground Sample Execution

how get count from mongodb with different status from one collection

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 ]
}
}
} }
])

Partition of Data with MongoDB

I have following collection
[
{
"setting": "Volume",
"_id": ObjectId("5a934e000102030405000000"),
"counting": 1
},
{
"setting": "Brightness",
"_id": ObjectId("5a934e000102030405000001"),
"counting": 1
},
{
"setting": "Contrast",
"_id": ObjectId("5a934e000102030405000002"),
"counting": 1
},
{
"setting": "Contrast",
"_id": ObjectId("5a934e000102030405000003"),
"counting": 1
},
{
"setting": "Contrast",
"_id": ObjectId("5a934e000102030405000004"),
"counting": 0
},
{
"setting": "Sharpness",
"_id": ObjectId("5a934e000102030405000005"),
"counting": 1
},
{
"setting": "Sharpness",
"_id": ObjectId("5a934e000102030405000006"),
"counting": 1
},
{
"setting": "Language",
"_id": ObjectId("5a934e000102030405000007"),
"counting": 1
},
{
"setting": "Language",
"_id": ObjectId("5a934e000102030405000008"),
"counting": 0
}
]
Now I want to group by setting and want only top most two data in result rest in useless
So my output should be after sort by counting
[
{
"setting": "Contrast",
"counting": 2
},
{
"setting": "Sharpness",
"counting": 2
},
{
"setting": "Useless",
"counting": 3
}
]
If you can get away with it, then it's probably best to "stuff" the reduced results into a single document and then $slice the top two and $sum the rest:
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$group": {
"_id": null,
"data": { "$push": "$$ROOT" }
}},
{ "$addFields": {
"data": {
"$let": {
"vars": { "top": { "$slice": ["$data", 0, 2 ] } },
"in": {
"$concatArrays": [
"$$top",
{ "$cond": {
"if": { "$gt": [{ "$size": "$data" }, 2] },
"then":
[{
"_id": "Useless",
"counting": {
"$sum": {
"$map": {
"input": {
"$filter": {
"input": "$data",
"cond": { "$not": { "$in": [ "$$this._id", "$$top._id" ] } }
}
},
"in": "$$this.counting"
}
}
}
}],
"else": []
}}
]
}
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
If it's potentially a very "large" result even reduced, then $limit use a $facet for the "rest":
Model.aggregate([
{ "$facet": {
"top": [
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 }
],
"rest": [
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$skip": 2 },
{ "$group": {
"_id": "Useless",
"counting": { "$sum": "$counting" }
}}
]
}},
{ "$project": {
"data": {
"$concatArrays": [
"$top","$rest"
]
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
Or even $lookup with MongoDB 3.6:
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 },
{ "$group": {
"_id": null,
"top": { "$push": "$$ROOT" }
}},
{ "$lookup": {
"from": "colllection",
"let": { "settings": "$top._id" },
"pipeline": [
{ "$match": {
"$expr": {
"$not": { "$in": [ "$setting", "$$settings" ] }
}
}},
{ "$group": {
"_id": "Useless",
"counting": { "$sum": "$counting" }
}}
],
"as": "rest"
}},
{ "$project": {
"data": {
"$concatArrays": [ "$top", "$rest" ]
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" } }
])
All pretty much the same really, and all return the same result:
{ "_id" : "Contrast", "counting" : 2 }
{ "_id" : "Sharpness", "counting" : 2 }
{ "_id" : "Useless", "counting" : 3 }
Optionally $project right at the end of each instead of the $replaceRoot if control over the field names is really important to you. Generally I just stick with the $group defaults
In the event that your MongoDB predates 3.4 and the resulting "Useless" remainder is actually too large to use any variant of the first approach, then simple Promise resolution is basically the answer, being one for the aggregate and the other for a basic count and simply do the math:
let [docs, count] = await Promise.all([
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 },
]),
Model.count().exec()
]);
docs = [
...docs,
{
"_id": "Useless",
"counting": count - docs.reduce((o,e) => o + e.counting, 0)
}
];
Or without the async/await:
Promise.all([
Model.aggregate([
{ "$group": {
"_id": "$setting",
"counting": { "$sum": "$counting" }
}},
{ "$sort": { "counting": -1 } },
{ "$limit": 2 },
]),
Model.count().exec()
]).then(([docs, count]) => ([
...docs,
{
"_id": "Useless",
"counting": count - docs.reduce((o,e) => o + e.counting, 0)
}
]).then( result => /* do something */ )
Which is basically a variation on the age old "total pages" approach by simply running the separate query to count the collection items.
Running separate requests is generally the age old way of doing this and it often performs best. The rest of the solutions are essentially aimed at "aggregation tricks" since that was what you were asking for, and that's the answer you got by showing different variations on the same thing.
One variant put's all results into a single document ( where possible, due to the BSON limit of course ) and the others basically vary on the "age old" approach by running the query again in a different form. $facet in parallel and $lookup in series.

Mongoose aggregate output format

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
}

Resources