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