I am trying to compute a percentage in a MongoDB query based on computed fields - not sure if this is possible or not. What I'd like to be able to do is calculate the failure percentage: (failed count / total) * 100
Here are a few sample documents:
{
"_id" : ObjectId("52dda5afe4b0a491abb5407f"),
"type" : "build",
"time" : ISODate("2014-01-20T22:39:43.880Z"),
"data" : {
"buildNumber" : 30,
"buildResult" : "SUCCESS"
}
},
{
"_id" : ObjectId("52dd9fede4b0a491abb5407a"),
"type" : "build",
"time" : ISODate("2014-01-20T22:15:07.901Z"),
"data" : {
"buildNumber" : 4,
"buildResult" : "FAILURE"
}
},
{
"_id" : ObjectId("52dda153e4b0a491abb5407b"),
"type" : "build",
"time" : ISODate("2014-01-20T22:21:07.790Z"),
"data" : {
"buildNumber" : 118,
"buildResult" : "SUCCESS"
}
}
Here is the query I am trying to work with. The issue is in the FailPercent/$divide line:
db.col.aggregate([
{ $match: { "data.buildResult" : { $ne : null } } },
{ $group: {
_id: {
month: { $month: "$time" },
day: { $dayOfMonth: "$time" },
year: { $year: "$time" },
},
Aborted: { $sum: { $cond : [{ $eq : ["$data.buildResult", "ABORTED"]}, 1, 0]} },
Failure: { $sum: { $cond : [{ $eq : ["$data.buildResult", "FAILURE"]}, 1, 0]} },
Unstable: { $sum: { $cond : [{ $eq : ["$data.buildResult", "UNSTABLE"]}, 1, 0]} },
Success: { $sum: { $cond : [{ $eq : ["$data.buildResult", "SUCCESS"]}, 1, 0]} },
Total: { $sum: 1 },
FailPercent: { $divide: [ "Failure", "Total" ] }
} },
{ $sort: { "_id.year": 1, "_id.month": 1, "_id.day": 1 } }
])
Because of this, if you have a simple aggregation pipeline or one which does not cut down the data volume much it can often be quicker to use a find() and perform the aggregation client side.
In MongoDB, aggregation operations process the data records/documents and return computed results. It collects values from various documents and groups them together and then performs different types of operations on that grouped data like sum, average, minimum, maximum, etc to return a computed result.
With aggregate + $match, you get a big monolithic BSON containing all matching documents. With find, you get a cursor to all matching documents. Then you can get each document one by one.
You almost got it. Only change that would be required is that you'll have to compute the FailPercent
in an additional project
phase, because the total is only available after the completion of the group
phase. Try this:
db.foo.aggregate([
{ $match: { "data.buildResult" : { $ne : null } } },
{ $group: {
_id: {
month: { $month: "$time" },
day: { $dayOfMonth: "$time" },
year: { $year: "$time" },
},
Aborted: { $sum: { $cond : [{ $eq : ["$data.buildResult", "ABORTED"]}, 1, 0]} },
Failure: { $sum: { $cond : [{ $eq : ["$data.buildResult", "FAILURE"]}, 1, 0]} },
Unstable: { $sum: { $cond : [{ $eq : ["$data.buildResult", "UNSTABLE"]}, 1, 0]} },
Success: { $sum: { $cond : [{ $eq : ["$data.buildResult", "SUCCESS"]}, 1, 0]} },
Total: { $sum: 1 }
} },
{$project:{Aborted:1, Failure:1, Unstable:1, Success:1, Total:1, FailPercent: { $divide: [ "$Failure", "$Total" ]}}},
{ $sort: { "_id.year": 1, "_id.month": 1, "_id.day": 1 } }
])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With