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.
Related
I have data in the following format:
{
"_id" : ObjectId("5f281caf3494701c38711e96"),
"WidgetId" : "0233261",
"Specs" : [
{
"WidgetType" : "A",
"AmountLow" : NumberLong(0),
"AmountHigh" : NumberLong(0),
},
{
"WidgetType: "A",
"AmountLow" : NumberLong(0),
"AmountHigh" : NumberLong(500),
},
{
"WidgetType" : "A"
"AmountLow" : NumberLong(1),
"AmountHigh" : NumberLong(1000),
}
]
}
The data however is wrong in that I can't have a value of "Specs.AmountLow" = 0 and "Specs.AmountHigh" > 0, they should either be 0/0 or >0/>0.
I am having no luck finding the documents which have a specific combination of "Specs.AmountLow" = 0 and "Specs.AmountHigh" > 0. Here are two queries I've attempted without success:
Attempt 1:
db.widgets.find(
{
"Specs.AmountLow" : NumberLong(0),
"Specs.AmountHigh" : {
"$gt" : NumberLong(0)
}
},
{
"WidgetId" : 1.0,
"Specs.AmountLow" : 1.0,
"Specs.AmountHigh" : 1.0
}
)
The above query send back all results as long as the AmountLow is 0 or the AmountHigh is greater than 0, so in the example data, all of the array values are matched, even if there's no 0/>0 value
I tried this one next:
db.widgets.find(
{
"$and" : [
{
"Specs.$.AmountLow" : NumberLong(0)
},
{
"Specs.$.AmountHigh" : {
"$gt" : NumberLong(0)
}
}
]
},
{
"WidgetId" : 1.0,
"Specs.AmountLow" : 1.0,
"Specs.AmountHigh" : 1.0
}
);
This one however didn't return any results, even when I had data confirmed with a 0/>0 value
How to I write a query that finds the specific sub-document combination of AmountLow = 0 and AmountHigh > 0 and, as a corollary, how do I update ONLY those records to have AmountLow = 1?
Expected result:
{
"_id" : ObjectId("5f281caf3494701c38711e96"),
"WidgetId" : "0233261",
"Specs" : [
{
"WidgetType: "A",
"AmountLow" : NumberLong(0),
"AmountHigh" : NumberLong(500),
}
]
}
You can try,
When you use $-positional this will return only one matching document from array and in array
use $elemMatch for array element matching
use $ positional after array field name in projection,
db.widgets.find({
Specs: {
$elemMatch: {
AmountLow: 0,
AmountHigh: {
$gt: 0
}
}
}
},
{
_id: 1,
WidgetId: 1,
"Specs.$": 1
})
Playground
Instead of above example you can use aggregate(), this example will return all matching documents from array,
$filter to get filtered documents from array on the base of conditions
db.widgets.aggregate([
{
$addFields: {
Specs: {
$filter: {
input: "$Specs",
cond: {
$and: [
{ $eq: ["$$this.AmountLow", 0] },
{ $gt: ["$$this.AmountHigh", 0] }
]
}
}
}
}
}
])
Playground
I have the following documents:
{ "_id" : ObjectId("5d9db4462034bf17454d7d33"), "name" : "Product1", "cost_oneoff" : "1", "cost_monthly" : "1", "margin_oneoff" : "1", "margin_monthly" : "1", "price_oneoff" : "1", "price_monthly" : "1" }
{ "_id" : ObjectId("5d9dc2f2d8e17309b46f9b03"), "name" : "Product2", "cost_oneoff" : "0", "cost_monthly" : "1", "margin_oneoff" : "0,5", "margin_monthly" : "0,5", "price_oneoff" : "0", "price_monthly" : "2" }
I want the sum of e.g. cost monthly with the following statement:
{ "_id" : null, "total" : 0 }
Can someone help me?
db.service_items.aggregate([
{ $match: {$or: [{"_id": ObjectId("5d9db4462034bf17454d7d33")},{"_id": ObjectId("5d9dc2f2d8e17309b46f9b03")}]}},
{ $group:
{_id: null,
total: {
$sum: "$cost_monthly"
}
}
}
])
Result:
{ "_id" : null, "total" : 0 }
The desired answer is 2
The $sum operator only works on an integer. According to the docs it ignores non-numeric values. You seem to have them stored as a string. Change cost_monthly to an integer and you should get the desired result:
"cost_monthly" : 1
You can check it out here.
Like #silencedogood said, The $sum operator only works on an integer. We need to convert the string to a numeric value using $toInt operator.
The following is an example:
db.service_items.aggregate([
{
$match: {
$or: [
{
"_id": ObjectId("5d9db4462034bf17454d7d33")
},
{
"_id": ObjectId("5d9dc2f2d8e17309b46f9b03")
}
]
}
},
{
$group: {
"_id": null,
"total": {
$sum: {
$toInt: "$cost_monthly"
}
}
}
}
])
Note: The $toInt is introduced in Mongo v4.0
I have the following records:
{ "_id" : 1, "c" : 120, "b" : [ { "f1" : 10 }, { "f1" : 10 } ] }
{ "_id" :2, "c" : 5, "b" : [ { "f1" : 10 }, { "f1" : 10 } ] }
I need the output this way:
{ "_id" : 1, 'total':140}
{ "_id" :2, 'total':25 }
where total = sum of value in 'c' with sum of values in f1 for same record.
When i unwind the field 'b' it creates two documents with same id and hence data is duplicated and when i sum it up, i get:
db.test2.aggregate([
{'$unwind':'$b'},
{'$project':{'total':{'$add':['$c','$b.f1']}}},
{'$group':{'_id':'$_id', 'total':{'$sum':'$total'}}}
])
outputs:
{ "_id" : 1, 'total':260}
{ "_id" :2, 'total':30 }
(not what i wanted, as it has added 120 and 5 again to total due to duplication during unwinding)
So i tried:
db.test2.aggregate([
{'$unwind':'$b'},
{'$group':{'_id':'$_id', 'c':{'$push': '$c'},'f1':{'$sum':'$b.f1'}}},
{'$project':{'total':{'$add':[{'$arrayElemAt':['$c',0]},'$f1']}}}
])
outputs:
{ "_id" : 1, 'total':140}
{ "_id" :2, 'total':25 }
( what i wanted)
Is there any other way to achieve this?
You can try below query. Sum operator to first calculate sum in array followed by add to calculate total with other field.
db.test2.aggregate([{
$project: {
total: {"$add":["$c", {"$sum":"$b.f1"}]}
}
}]
An alternative:
db.test2.aggregate([{
$project: {
_id: 0,
c: "$c",
b: {
$reduce: {
input: "$b.f1",
initialValue: 0,
in: {
$add: ["$$value", "$$this"]
}
}
}
}
},
{
$project: {
_id: 0,
total: {
$sum: ["$c", "$b"]
}
}
}
])
That would create result:
{
"total" : 140
}
{
"total" : 25
}
If you need the field _id then replace the _id: 0 in both $project to _id: 1
That would create this result:
{
"_id" : 1,
"total" : 140
}
{
"_id" : 2,
"total" : 25
}
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
}