Sum all named fields in aggregation - mongodb

I am trying to calculate the sum of all the values below. I have tried googling the question in different ways but cannot find an answer. The data looks like this.
I don't care about the keys, I am just looking for a total of the values for monday
"monday" : {
"a" : 5,
"b" : 2,
"c" : 1,
"d" : 2,
"e" : 3,
"f" : 9,
"g" : 2,
"h" : 16,
"h2" : 8,
"g" : 2
}

You can use $objectToArray to convert monday into an array of k and v fields and then use $reduce to sum them:
db.collection.aggregate([
{
$project: {
sum: {
$reduce: {
input: { $objectToArray: "$monday" },
initialValue: 0,
in: { $add: [ "$$value", "$$this.v" ] }
}
}
}
}
])
Mongo playground

Related

Mongodb sum by field exists

I have the following collection with the number of fields changing over time.
{
"_id" : "9235#7421",
"usine" : { "0" : 0, "1" : 3, "2" : 1, "3" : 2, "4" : 0, "5" : 3, "6" : 1, "7" : 0, "8" : 2, "9" : 1 },
"ecole" : { "0" : 1, "1" : 0, "2" : 0, "3" : 1, "4" : 1, "5" : 0, "6" : 0, "7" : 1, "8" : 0, "9" : 1 }
}
The following query works fine. It calculates the sum of the values of all fields.
db.lieux.aggregate([
{
$match: {
"_id": "9235#7421"
}
},
{
$project: {
"MontoSum": {
$sum: {
$add: [
"$usine.0", "$usine.1", "$usine.2", "$usine.3", "$usine.4", "$usine.5", "$usine.6", "$usine.7", "$usine.8", "$usine.9", "$ecole.0", "$ecole.1", "$ecole.2", "$ecole.3", "$ecole.4", "$ecole.5", "$ecole.6", "$ecole.7", "$ecole.8", "$ecole.9"]
}
}
}
}
])
The result is: { "_id" : "9235#7421", "MontoSum" : 18 } and this is correct.
My questions are :
1) is there a better way to query the sum of values of the fields of my collection? I feel that my query is too long and not smart at all.
2) When a document is missing a field that is in my query (for example field "6" is missing), the returned value is { "_id" : "9235#7421", "MontoSum" : 0 } which is not correct. It seems to me that the sum function does not like missing fields. How can I use someting like $ifNull in my case?
Thank you very much
We can use operators like $objectToArray and $map to manipulate object structure:
{
$project: {
"MontoSum": {
$sum: {
$concatArrays: [
{
$map: {
input: {$objectToArray: "$usine"},
as: "usine",
in: "$$usine.v"
}
},
{
$map: {
input: {$objectToArray: "$ecole"},
as: "ecole",
in: "$$ecole.v"
}
}
]
}
}
}
}
Now this isn't exactly the shortest expression but it scales.
Using these operators will also fix this issue as you were summing an undefined value caused that unexpected behaviour.

Aggregation $group $sum fields among one document

Here is document example, year field contains year keys, that contains some metrics with included days as keys:
{
"_id" : NumberInt(1),
"year" : {
"2017" : {
"g1" : {
"1" : {
"total" : 2.0
},
"2" : {
"total" : 5.0
}
},
"g2" : {
"1" : {
"total" : 3.0
},
"2" : {
"total" : 6.0
}
}
}
}
I dont want getting document into memory to summarize total field for each key field g#.
How could i say to mongodb, summ total field for each key in year field.
Result that i want: g1 = 7.0, g2 = 9.0
You have to change your year part of structure to something like below.(Preferred)
"year" : [{ "k" : "2017", "v":[{ "k": "g1", "v":[{ "k" : "1","v" : {"total" : 2 }},{ "k" : "2","v" : {"total" : 5}}]}, { "k": "g2", "v":[{ "k" : "1","v" : {"total" : 3 }},{ "k" : "2","v" : {"total" : 6}}]}]}]
You can the below aggregation. This will work without knowing the keys ahead of time.
The query $unwinds couple of times to reach the g & total document followed by group on the g key and calculate total sum.
db.collection.aggregate([
{$match:{_id:1}},
{$unwind:"$year"},
{$unwind:"$year.v"},
{$unwind:"$year.v.v"},
{
$group:
{
_id:"$year.v.k",
sum: {$sum:"$year.v.v.v.total"}
}
}
])
This is the solution if you can't change your structure.
You can use 3.4.4 version and use $objectToArray to convert all the dynamic keys into labeled key and value pair.
Stage 1 & 2: Match on _id filter and convert the dynamic year keys into label value pair.
Stage 3 & 4: $unwind year array & $reduce the total value to calculate sum before changing the g1 and g2 dynamic keys to labeled key and value pair.
db.collection.aggregate([
{$match:{_id:1}},
{$addFields: {"year": {$objectToArray: "$year"}}},
{$unwind:"$year"},
{
$project:
{
g1:
{
$reduce: {
input: {$objectToArray: "$year.v.g1"},
initialValue: 0,
in: { $sum: [ "$$value", "$$this.v.total" ] }
}
},
g2:
{
$reduce: {
input: {$objectToArray: "$year.v.g2"},
initialValue: 0,
in: { $sum: [ "$$value", "$$this.v.total" ] }
}
}
}
}
])

Are there computed fields in MongoDB? [duplicate]

This question already has answers here:
MongoDB - The argument to $size must be an Array, but was of type: EOO / missing
(3 answers)
Closed 5 years ago.
Are there computed fields in MongoDB?
In SQL I can do:
SELECT A+B AS C FROM MYTABLE WHERE C>10
Can I do something similar in MongoDB?
UPDATE
I did with projection:
db.segments.aggregate(
[
{
$project: {
"_id": 1,
numberOfRestrictions: { $size: "$Speed Restrictions" }
}
}
]
)
and it works.
Unfortunately, further pipelining does not:
db.segments.aggregate(
[
{
$project: {
"_id": 1,
numberOfRestrictions: { $size: "$Speed Restrictions" }
}
},
{
$match: {
"numberOfRestrictions": {
"$gt": 1
}
}
}
]
)
Latter causes error
The argument to $size must be an Array, but was of type: EOO
Yes. It is called aggregation pipelines. Specifically, you need to use a $project stage to create the C field, and then use a $match stage to find all documents which match the criterion.
Example
Let's create some documents first:
for( var i = 1; i <=10; i++){
db.agg.insert({a:i,b:i})
}
Which results in a collection looking like this:
> db.agg.find()
{ "_id" : ObjectId("56c1b5561a3b578f37a99d4d"), "a" : 1, "b" : 1 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d4e"), "a" : 2, "b" : 2 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d4f"), "a" : 3, "b" : 3 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d50"), "a" : 4, "b" : 4 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d51"), "a" : 5, "b" : 5 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d52"), "a" : 6, "b" : 6 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d53"), "a" : 7, "b" : 7 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d54"), "a" : 8, "b" : 8 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d55"), "a" : 9, "b" : 9 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d56"), "a" : 10, "b" : 10 }
Finding all documents for which C > 10
db.agg.aggregate([
// You need to include all fields you want to have
// in the resulting document within the $project stage
{ "$project":{ a:1, b:1, c:{ "$add": ["$a","$b"] }}},
{ "$match":{ c:{ "$gt":10 }}}
])
Returns the following result:
{ "_id" : ObjectId("56c1b5561a3b578f37a99d52"), "a" : 6, "b" : 6, "c" : 12 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d53"), "a" : 7, "b" : 7, "c" : 14 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d54"), "a" : 8, "b" : 8, "c" : 16 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d55"), "a" : 9, "b" : 9, "c" : 18 }
{ "_id" : ObjectId("56c1b5561a3b578f37a99d56"), "a" : 10, "b" : 10, "c" : 20 }
There is an operator called $expr that enables you to use aggregation framework operator inside the find() query.
For instance, the SQL query
SELECT A+B AS C FROM MYTABLE WHERE C>10
can be translated to a mongo query as
db.segments.find({
"$expr": {
"$gt": [
{ "$add": [ "$A", "$B" ] },
10
]
}
})
And for checking an array length it's similar
db.segments.find({
"$expr": {
"$gt": [
{ "$size": "$SpeedRestrictions" },
10
]
}
})
With the aggregation framework it's also possible to use $expr within a $match pipeline step:
db.segments.aggregate([
{ "$match": {
"$expr": {
{ "$gt": [
{ "$size": "$SpeedRestrictions" },
10
] }
}
} }
])
And if the $expr operator is not available, for backwards compatibility one can use $redact as
db.segments.aggregate([
{ "$redact": {
"$cond": [
{ "$gt": [
{ "$size": "$SpeedRestrictions" },
10
] },
"$$KEEP",
"$$PRUNE"
]
} }
])
The other approach is to use the $addFields pipeline operator for creating the computed fields and the $match operator for filtering documents based on that computed field:
db.collection.aggregate([
{ "$addFields": { "C": { "$add": [ "$A", "$B" ] } } },
{ "$match": { "C": { "$gt": 10 } } }
])

MongoDB Aggregate Variable to create Multiple Fields?

Given the following query, what is the best method to use $$priceToInflationRatio to help create multiple calculated fields? From what I have read on $let, it appears to only work for creating a single field -- I would like to use the variables across my entire $project section. Is that possible?
db.books.aggregate([
{$project: {
'priceInflationresult': {
$let: {
vars: {
'priceToInflationRatio': {
$multiply: [{'$divide': [{'$subtract': ['$price', 1]}, 5]}, 10]
}
},
in: {
'$cond': [
{'$gt': ['$price', 5]},
{'$mod': ['$$priceToInflationRatio', 1]},
1
]
},
}
}
}}
])
The in part of a $let expression is an object, so it can accept multiple keys, each of which can be an expression that is evaluated with the variables in scope:
> db.test.insert({ "_id" : 0, "a" : 1, "b" : 1 })
> db.test.aggregate([{
"$project" : {
"test" : {
"$let" : {
"vars" : {
"c" : 2,
"d" : 3
},
"in": {
"a" : { "$add" : ["$a", "$$c"] },
"b" : { "$add" : ["$b", "$$d"] }
}
}
}
}
}]);
{ "_id" : 0, "test" : { "a" : 3, "b" : 4 } }
Note that this will necessarily create subdocuments as top-level $let expressions are not allowed. You can change this with another $project stage.

mongodb aggregation cast to int

I have the following problem with mongo using the aggregation framework.
Suppose and item with time in seconds, t, and an event id occurring, e, like:
item:{t:11433, e:some_id}
what I want is to aggregate according to t and e. It means counting the number of id 'e' in a time t.
This is easy to do using the aggregation with $group.
However, I would like to have a different time course. For example, I want to count number of same event id in a time slot of eg. 5 seconds. I could do this progammatically, in js or python . I was just wondering if it could work using just mongo, using a cascade of group.
I tried to project using $divide[t,10]. For 11433, this would give, 1143.3 But it seems that I can't remove the 0.3 in Mongo (Otherwise I could group in this other scale).
Any hint?
thanks
To get an integer group key for a 5-second interval, you could use the formula
t = t - (t % 5) // % is the modula operator
In the aggregation framework this would look like this:
db.xx.aggregate([
// you need two projections, as they can not be nested
// this does not work:
// { $project: { _id: 0, e: 1, t: 1, tk: { $subtract: [ "$t", $mod: [ "$t", 5 ] ] } } },
//
// get modula 5 of time in seconds:
{ $project: { _id: 0, e: 1, t: 1, tm5: { $mod: [ "$t", 5 ] } } },
// subtract it from time:
{ $project: { _id: 0, e: 1, ti: { $subtract: [ "$t", "$tm5" ] } } },
// now group on e and interval,
{ $group: { _id: { e: "$e", interval: "$ti" }, count: { $sum: 1 } } },
])
For this example collection:
> db.xx.find()
{ "_id" : ObjectId("515e5a7157a0887a97cc8d1d"), "t" : 11433, "e" : "some_id" }
{ "_id" : ObjectId("515e60d457a0887a97cc8d1e"), "t" : 11434, "e" : "some_id" }
{ "_id" : ObjectId("515e60d857a0887a97cc8d1f"), "t" : 11438, "e" : "some_id" }
the result is:
{
"result" : [
{
"_id" : {
"e" : "some_id",
"interval" : 11435
},
"count" : 1
},
{
"_id" : {
"e" : "some_id",
"interval" : 11430
},
"count" : 2
}
],
"ok" : 1
}