MongoDB - Subtracting Double in Aggregation Pipeline - mongodb

I'll start with the part of the query that's not working right.
{$project: {"date":"$_id.date",
"location":"$data.location",
"coachgroup":"$data.coachgroup",
"staffedcoaches":"$data.staffedcoaches",
"allots":{
$cond: [
{$eq: [
"$data.coachgroup","Default"
]},
{$floor: [
{$subtract:
[NumberInt("$data.staffcoaches"),
{$divide:
["$data.virtualheads",
"$data.ratio"
]}
]}
]},
"$data.allots"
]}
}
},
So, the "staffedcoaches" field is a Double. "virtualheads" and "ratio" are both Int32. "staffedcoaches" is derived from a count much earlier in the query. When I try to use it as-is in the subtraction, the result is null. When I try to use it with NumberInt() as shown below the staffedcoaches becomes a 0. Number() and NumberLong() are no more successful.
To take just one document as an example, the "ratio" is 15 and the staffedcoaches is 4. So the result should be 11. But I can only get a null or a -15.
Is there a way to get the Double treated as an Integer so I can complete this operation?
Thanks for reading

I figured out the answer. While the calculation wouldn't work in $project, it did work as an $addFields. (You may notice I took out the conditional. With or without it makes no difference. The only impacting factor was moving the calculations from $project to $addFields.)
{$project: {"date":"$_id.date",
"location":"$data.location",
"coachgroup":"$data.coachgroup",
"staffedcoaches":"$data.staffedcoaches"
},
{$addFields: {
"allots":{
$floor:[
{$subtract:
["$staffedcoaches",
{$divide:
["$virtualheads","$ratio"]}
]}]
}}},

Related

MongoDB queries - $condition by element in array

I have the following aggregation pipeline consisting of a single $redact step:
(which is supposed to return all products for which the recorded number of sales exceeds the stock)
$redact:
{
$cond: [
{ $gt: ["$sales", "$productInfo.0.stock"] },
"$$KEEP",
"$$PRUNE"
]
}
(the syntax above is specific to Compass, so no issues here)
Where the entities look something like:
{
_id: 123,
sales: 60,
price: 80,
productInfo: [
{
stock: 100
}
]
}
However, the query above does not seem to work. My presumption is that the issue is caused by the comparison with $productInfo.0.stock, as replacing it with another direct attribute of the entity (e.g. price) runs without any issues.
How should such a query ($cond by $gt where one of the values is from an array/list) be specified?
The productionInfo.0.stock syntax is the Mongo Query Language (MQL), which can be used in a $match or a find.
That particular construct is not availabl when using aggregation syntax, such as "$fieldName". Instead, you need to use $arrayElemAt, but that unfortunately doesn't support accessing fields in the reference element.
To get what you want, you will need to add a field in a prior stage that retrieves the desired element from the array, reference that object in the $redact, and then project out the temporary field, such as:
{$addFields: {firstProduct: {$arrayElemAt: [ "$productInfo", 0 ]}}},
{$redact:{
$cond: [
{ $gt: ["$sales", "$productInfo.0.stock"] },
"$$KEEP",
"$$PRUNE"
]
}},
{$project: {firstProduct: 0}}

How do I project an element of an array in mongo?

I have a mongo document that contains something like
{date: [2018, 3, 22]}
and when I try to project this into a flat JSON structure with these fields concatenated, I always get an array with 0 elements, eg. just extracting the year with
db.getCollection('blah').aggregate([
{$project: {year: "$date.0"}}
])
I get
{"year" : []}
even though matching on a similar expression works fine, eg.
db.getCollection('blah').aggregate([
{$match: {"$date.0": 2018}}
])
selects the documents I would expect just fine.
What am I doing wrong? I've searched mongo documentation and stackoverflow but could find nothing.
For $project you should use $arrayElemAt instead of dot notation which works only for queries.
db.getCollection('blah').aggregate([
{$project: {year: { $arrayElemAt: [ "$date", 0 ] }}}
])
More here

Can you do a MongoDB aggregate projection operator $in

Can you do a MongoDB aggregate projection operator {$cond: { '$field': {$in: ['val1', 'val2']}}}? Or I have to break it into $or?
Currently I have a:
$project: {
countNames: [
// if
{$or: [
{$eq: ['$name', 'Frist variant']},
{$eq: ['$name', 'Second variant']}
]
// then
1,
// else
0
]
}
I just wonder if there is a way to avoid those repeating $eq's, something like:
sum: {
$cond: [
'$name': {$in: ['Frist', 'Second']},
1,
0
]
}
Okay, I know that the version I wrote doesn't work (tried it), but is there an operator that can do it? Repeating $eq's gets old quickly when there are more of them, I sometimes resort to multi-stage pipe, or mapReduce instead. This would help up with some of my code.
For what it's worth, I'm currently on MongoDB 2.6, but I plan to switch production to Mongo 3 within weeks, so v3 solution will also be acceptable.

MongoDB: calculate average value for the document & then do the same thing across entire collection

I have collection of documents (Offers) with subdocuments (Salary) like this:
{
_id: ObjectId("zzz"),
sphere: ObjectId("xxx"),
region: ObjectId("yyy"),
salary: {
start: 10000,
end: 50000
}
}
And I want to calculate average salary across some region & sphere for the entire collection. I created query for this, it works, but it takes care only about salary start value.
db.offer.aggregate(
[
{$match:
{$and: [
{"salary.start": {$gt: 0}},
{region: ObjectId("xxx")},
{sphere: ObjectId("yyy")}
]}
},
{$group: {_id: null, avg: {$avg: "$salary.start"}}}
]
)
But firstly I want to calculate avarage salary (start & end) of the offer. How can I do this?
Update.
If value for "salary.end" may be missing in your data, you need to add one additional "$project" iteration to replace missing "salary.end" with existing "salary.start". Otherwise, the result of the average function will be calculated wrong due to ignoring documents with the lack of "salary.end" values.
db.offer.aggregate([
{$match:
{$and: [
{"salary.start": {$gt: 0}},
{"region": ObjectId("xxx")},
{"sphere": ObjectId("yyy")}
]}
},
{$project:{"_id":1,
"sphere":1,
"region":1,
"salary.start":1,
"salary.end":1,
"salary.end": {$ifNull: ["$salary.end", "$salary.start"]}
}
},
{$project:{"_id":1,
"sphere":1,
"region":1,
"avg_salary":{$divide:[
{$add:["$salary.start","$salary.end"]}
,2
]}}},
{$group:{"_id":{"sphere":"$sphere","region":"$region"},
"avg":{$avg:"$avg_salary"}}}
])
The way you aggregate has to be modified:
Match the required region,sphere and where salary > 0.
Project a extra field for each offer, which holds the average of
start and end.
Now group together the records with the same region and sphere, and
apply the $avg aggregation operator on the avg_salary for each offer
in that group,to get the average salary.
The Code:
db.offer.aggregate([
{$match:
{$and: [
{"salary.start": {$gt: 0}},
{"region": ObjectId("xxx")},
{"sphere": ObjectId("yyy")}
]}
},
{$project:{"_id":1,
"sphere":1,
"region":1,
"avg_salary":{$divide:[
{$add:["$salary.start","$salary.end"]}
,2
]}}},
{$group:{"_id":{"sphere":"$sphere","region":"$region"},
"avg":{$avg:"$avg_salary"}}}
])

Mongodb aggregate query help - grouping with multiple fields and converting to an array

I have the following document in the mongodb collection
[{quarter:'Q1',project:'project1',user:'u1',cost:'100'},
{quarter:'Q2',project:'project1',user:'u2',cost:'100'},
{quarter:'Q3',project:'project1',user:'u1',cost:'200'},
{quarter:'Q1',project:'project2',user:'u2',cost:'200'},
{quarter:'Q2',project:'project2',user:'u1',cost:'300'},
{quarter:'Q3',project:'project2',user:'u2',cost:'300'}]
i need to generate an output which will sum the cost based on quarter and project and put it in the format so that it can be rendered in the Extjs chart.
[{quarter:'Q1','project1':100,'project2':200,'project3':300},
{quarter:'Q2','project1':100,'project2':200,'project3':300},
{quarter:'Q3','project1':100,'project2':200,'project3':300}]
i have tried various permutations and combinations of aggregates but couldnt really come up with a pipeline. your help or direction is greatly appreciated
Your cost data appears to be strings, which isn't helping, but assuming you're around that:
The main component is the $cond operator in the document projection, and assuming your data is larger and you want to group the results:
db.mstats.aggregate([
// Optionaly match first depending on what you are doing
// Sum up cost for each quarter and project
{$group: {_id: { quarter: "$quarter", project: "$project" }, cost: {$sum: "$cost" }}},
// Change the "projection" in $group, using $cond to add a key per "project" value
// We use $sum and the false case of 0 to fill in values not in the row.
// These will then group on the key adding the real cost and 0 together.
{$group: {
_id: "$_id.quarter",
project1: {$sum: {$cond:[ {$eq: [ "$_id.project", "project1" ]}, "$cost", 0 ]}},
project2: {$sum: {$cond:[ {$eq: [ "$_id.project", "project2" ]}, "$cost", 0 ]}}
}},
// Change the document to have the "quarter" key
{$project: { _id:0, quarter: "$_id", project1: 1, project2: 1}},
// Optionall sort by quarter
{$sort: {quarter: 1 }}
])
So after doing the initial grouping the document is altered with use of $cond to determine if the value of a key is going to go into the new key name. Essentially this asks if the current value of project is "project1" then put the cost value into this project1 key. And so on.
As we put a 0 value into this new document key when there was no match, we need to group the results again in order to merge the documents. Sorting is optional, but probably what you want for a chart.
Naturally you will have to build this up dynamically and probably query for the project keys that you want. But otherwise this should be what you are looking for.