Get average value from array consisting of objects based on objects fields - mongodb

I have a document of the following structure:
{
"_id" : ObjectId("598446bb13c7141f1"),
"trackerId" : "598446ba-fa9b-4000-8000-4ea290e",
"powerMetrics" : [
{
"duration" : 0.15,
"powerConsumption" : 0.1
},
{
"duration" : 0.1,
"powerConsumption" : 0.05
}
]
}
My goal is to get another document, which would contain a single value avgMetric. This avgMetrics should be calculated using powerMetrics array in the following way:
(powerMetrics[0].powerConsumption/powerMetrics[0].duration
+ powerMetrics[1].powerConsumption/powerMetrics[1].duration) / powerMetrics.size()
So this avgMetrics should represent the average of all (powerConsumption/duration) from the powerMetrics array.
After experimenting with query I could not achieve this,
Size of the powerMetrics array can vary, Mongo db version is 3.2.14
Could someone please help with that?
Thanks

You can use $map to with $avg to output avg in 3.2 mongo version.
db.col_name.aggregate(
[{"$project":{
"avgMetrics":{
"$avg":{
"$map":{
"input":"$powerMetrics",
"as":"val",
"in":{"$divide":["$$val.powerConsumption","$$val.duration"]}
}
}
}
}}])

db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: "$powerMetrics",
preserveNullAndEmptyArrays: true // optional
}
},
// Stage 2
{
$group: {
_id: '$_id',
metrics: {
$addToSet: {
$divide: ['$powerMetrics.powerConsumption', '$powerMetrics.duration']
}
}
}
},
// Stage 3
{
$project: {
avgVal: {
$avg: '$metrics'
}
}
},
]
);

Related

MongoDB - count by field, and sort by count

I am new to MongoDB, and new to making more than super basic queries and i didn't succeed to create a query that does as follows:
I have such collection, each document represents one "use" of a benefit (e.g first row states the benefit "123" was used once):
[
{
"id" : "1111",
"benefit_id":"123"
},
{
"id":"2222",
"benefit_id":"456"
},
{
"id":"3333",
"benefit_id":"456"
},
{
"id":"4444",
"benefit_id":"789"
}
]
I need to create q query that output an array. at the top is the most top used benefit and how many times is was used.
for the above example the query should output:
[
{
"benefit_id":"456",
"cnt":2
},
{
"benefit_id":"123",
"cnt": 1
},
{
"benefit_id":"789",
"cnt":1
}
]
I have tried to work with the documentation and with $sortByCount but with no success.
$group
$group by benefit_id and get count using $sum
$sort by count descending order
db.collection.aggregate([
{
$group: {
_id: "$benefit_id",
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } }
])
Playground
$sortByCount
Same operation using $sortByCount operator
db.collection.aggregate([
{ $sortByCount: "$benefit_id" }
])
Playground

MongoDB aggregate with total count as variable

I have a condition which says: Create a mongodb query that pulls 5% of total settled claims by claims examinar and my document for example is:
claims collection
{
"_id" : ObjectId("5dbbb6b693f50332a533f4db"),
"active" : true,
"status" : "settled",
}
{
"_id" : ObjectId("5dbbb6b693f50332a533f4db"),
"active" : true,
"status" : "unsettled",
}
I can calculate the total like this.
db.getCollection("claims").aggregate([
{$match: { 'status': 'trm'}},
{ $count: "total"}
])
It gives me the count of 42 for example.
So what I am trying to achieve is calculate the total count of settled data, set the total as a variable and apply the 5% of the formula in the $limit section as
{
$limit: $total * 0.05
}
I am unable to set the total from one pipeline as the variable and apply that in another pipeline.
Help please. How to achieve this type of condition?
You can do this by adding project stage and using multiply operator like this:
db.getCollection("claims").aggregate([
{
$match: {
"status": "trm"
}
},
{
$count: "total"
},
{
$project: {
"total": {
$multiply: [
"$total",
0.95
]
}
}
}
])
https://mongoplayground.net/p/VsypWLI6iJt
Docs: https://docs.mongodb.com/manual/reference/operator/aggregation/multiply

MongoDB aggregate query to SpringDataMongoDB

I have below MongoDB aggregate query and would like to have it's equivalent SpringData Mongodb query.
MongoDB Aggregate Query :
db.response.aggregate(
// Pipeline
[
// Stage 1 : Group by Emotion & Month
{
$group: {
_id: {
emotion: "$emotion",
category: "$category"
},
count: {
$sum: 1
},
point: {
$first: '$point'
}
}
},
// Stage 2 : Total Points
{
$addFields: {
"totalPoint": {
$multiply: ["$point", "$count"]
}
}
},
// Stage3 : Group By Category - Overall Response Total & totalFeedbacks
{
$group: {
_id: '$_id.category',
totalFeedbacks: {
$sum: "$count"
},
overallResponseTotal: {
$sum: "$totalPoint"
}
}
},
// Stage4 - Overall Response Total & totalFeedbacks
{
$project: {
_id: 1,
overallResponseTotal: '$overallResponseTotal',
maxTotalFrom: {
"$multiply": ["$totalFeedbacks", 3.0]
},
percent: {
"$multiply": [{
"$divide": ["$overallResponseTotal", "$maxTotalFrom"]
}, 100.0]
}
}
},
// Stage4 - Percentage Monthwise
{
$project: {
_id: 1,
overallResponseTotal: 1,
maxTotalFrom: 1,
percent: {
"$multiply": [{
"$divide": ["$overallResponseTotal", "$maxTotalFrom"]
}, 100.0]
}
}
}
]
);
I have tried it's equivalent in Spring Data but got stuck at Stage 2 on how to convert "$addFields" to java code. Though I search about it on multiple sites but couldn't find anything useful. Please see my equivalent java code for Stage 1.
//Stage 1 -Group By Emotion and Category and return it's count
GroupOperation groupEmotionAndCategory = Aggregation.group("emotion","category").count().as("count").first("point")
.as("point");
Aggregation aggregation = Aggregation.newAggregation(groupEmotionAndCategory);
AggregationResults<CategoryWiseEmotion> output = mongoTemplate.aggregate(aggregation, Response.class, CategoryWiseEmotion.class);
Any helps will be highly appreciated.
$addFields is not yet supported by Spring Data Mongodb.
One workaround is to pass the raw aggregation pipeline to Spring.
But since you have a limited number of fields after stage 1, you could also downgrade stage 2 to a projection:
{
$project: {
// _id is included by default
"count" : 1, // include count
"point" : 1, // include point
"totalPoint": {
$multiply: ["$point", "$count"] // compute totalPoint
}
}
}
I haven't tested it myself, but this projection should translate to something like:
ProjectionOperation p = project("count", "point").and("point").multiply(Fields.field("count")).as("totalPoint");
Then you can translate stage 3, 4 and 5 similarly and pass the whole pipeline to Aggregation.aggregate().

Adding Some Extra calculation during the time of Sum on mongodb $sum function

My Mongo-Db dataset is this:
{
"_id" : ObjectId("5a267533754884223467604a"),
"user_id" : "5a20ee1acdacc7086ce7742c",
"tv_count" : 1,
"ac_count" : 0,
"fridge_count" : 0,
"blower_count" : 0,
"chair_count" : 0,
"sofa_count" : 0,
"D2H_count" : 2,
"lastmodified" : ISODate("2017-12-05T10:30:30.559Z"),
"__v" : 0
}
So I know i want to do some modification during the time of Sum.
My Sum Code is this:
Accessories.aggregate([
{$match: { "lastmodified":{$gt: newTime}}},
{
$project: {
total: {
$add: [ "$tv_count", "$ac_count", "$fridge_count", "$blower_count", "$chair_count", "$sofa_count", "$D2H_count"]
}
}
}
]);
So it will return the result
[ { _id: 5a267533754884223467604a, total: 3 } ]
Now I want to do some extra calculation. Example is
Earlier Result will be 1+0+0+0+0+0+2 = 3
My desire result will be like (1*2)+0+0+0+0+0+(2*4) = 10
Any Help will be appreciated.
You can simply include additional nested calculations inside your existing projection like so:
$project: {
total: {
$add: [ { $multiply: [ "$tv_count", 2 ] }, "$ac_count", "$fridge_count", "$blower_count", "$chair_count", "$sofa_count", { $multiply: [ "$D2H_count", 4 ] }]
}
}
You should not use the $addFields or any other additional stage for this type of thing, really, since adding stages will slow down the aggregation pipeline and adding fields in particular will inflate the interim result documents between the stages so again slow down your query.
I would add an $addFields stage in the aggregation pipeline between the $match and the #project stage.
In the $addFields stage you can define new "helper fields" - eg.
{ $addFields: {
"tv_temp": { $multiply: [ "$tv_count", 2 ] },
"D2H_temp": { $multiply: [ "$D2H_count", 4 ] },
}
}
Using these fields, you can now calculate the total:
{ $project: {
total: {
$add: [ "$tv_temp", "$ac_count", "$fridge_count", "$blower_count", "$chair_count", "$sofa_count", "$D2H_temp"]
}
}}

MongoDB Aggregation: Counting distinct fields

I am trying to write an aggregation to identify accounts that use multiple payment sources. Typical data would be.
{
account:"abc",
vendor:"amazon",
}
...
{
account:"abc",
vendor:"overstock",
}
Now, I'd like to produce a list of accounts similar to this
{
account:"abc",
vendorCount:2
}
How would I write this in Mongo's aggregation framework
I figured this out by using the $addToSet and $unwind operators.
Mongodb Aggregation count array/set size
db.collection.aggregate([
{
$group: { _id: { account: '$account' }, vendors: { $addToSet: '$vendor'} }
},
{
$unwind:"$vendors"
},
{
$group: { _id: "$_id", vendorCount: { $sum:1} }
}
]);
Hope it helps someone
I think its better if you execute query like following which will avoid unwind
db.t2.insert({_id:1,account:"abc",vendor:"amazon"});
db.t2.insert({_id:2,account:"abc",vendor:"overstock"});
db.t2.aggregate([
{ $group : { _id : { "account" : "$account", "vendor" : "$vendor" }, number : { $sum : 1 } } },
{ $group : { _id : "$_id.account", number : { $sum : 1 } } }
]);
Which will show you following result which is expected.
{ "_id" : "abc", "number" : 2 }
You can use sets
db.test.aggregate([
{$group: {
_id: "$account",
uniqueVendors: {$addToSet: "$vendor"}
}},
{$project: {
_id: 1,
vendorsCount: {$size: "$uniqueVendors"}
}}
]);
I do not see why somebody would have to use $group twice
db.t2.aggregate([ { $group: {"_id":"$account" , "number":{$sum:1}} } ])
This will work perfectly fine.
This approach doesn't make use of $unwind and other extra operations. Plus, this won't affect anything if new things are added into the aggregation. There's a flaw in the accepted answer. If you have other accumulated fields in the $group, it would cause issues in the $unwind stage of the accepted answer.
db.collection.aggregate([{
"$group": {
"_id": "$account",
"vendors": {"$addToSet": "$vendor"}
}
},
{
"$addFields": {
"vendorCount": {
"$size": "$vendors"
}
}
}])
To identify accounts that use multiple payment sources:
Use grouping to count data from multiple account records and group the result by account with count
Use a match case is to filter only such accounts having more than one payment method
db.payment_collection.aggregate([ { $group: {"_id":"$account" ,
"number":{$sum:1}} }, {
"$match": {
"number": { "$gt": 1 }
}
} ])
This will work perfectly fine,
db.UserModule.aggregate(
{ $group : { _id : { "companyauthemail" : "$companyauthemail", "email" : "$email" }, number : { $sum : 1 } } },
{ $group : { _id : "$_id.companyauthemail", number : { $sum : 1 } } }
);
An example
db.collection.distinct("example.item").forEach( function(docs) {
print(docs + "==>>" + db.collection.count({"example.item":docs}))
});