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.
Related
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}}
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"]}
]}]
}}},
How can I get documents from mongo with an array containing some elements but IN THE SAME ORDER?
I know that $all do the job but ignoring the order of elements. The order in my case is important and I can't sort my arrays since it's describing a path that I want to keep the order.
111,222,333 is not the same as 222,111,333
Is there a way to do it using $all or maybe another operator in mongo aggregation framework?
You can avoid the first "intersect" field, is just to give you back as debug what MongoDB make with this command. You should create the $and operator dynamically.
db.Test6.aggregate([
{
$project: {
_id:1,
pages:1,
intersect: {$setIntersection: [[111,666], "$pages"]},
theCondition: {$let: {
vars: {
intersect: {$setIntersection: [[111,666], "$pages"]}
},
in: {
$cond:[ {$and:[
{$eq:[{$arrayElemAt:["$$intersect", 0]}, 111]},
{$eq:[{$arrayElemAt:["$$intersect", 1]}, 666]}
]} , true, false]
}
}
}
}
}
]);
I have a rather a bit complex document, but here is the sample I can put it here.
I have 'addresses' array that has multiple addresses documents something like
addresses : [
{'code': '1', line:'abc town'}, {'code':'2', line:'bcd town'},{'code':'3', line: 'another town'}
]
and I want to pull the document based on the code. for example code:1 then just pull this one document if not look if there is code:2, yes then pull this document, else pull anything else is available
my document structure looks like this
[
_id:123
fn: 'name',
addresses : [
{'code': '1', line:'abc town'}, {'code':'2', line:'bcd town'},{'code':'3', line: 'another town'}
]
]
and the query I wrote is
db.collection.aggregate([{$match: {'_id':123}},
{$project: {
"fn" :1,
"addresses": {$filter: {
input: '$addresses',
as: 'address',
cond: {$eq: ['$$address.code', '1']}
}},
_id: 0
}}
it works when the code is equal to 1 (obviously) but I am not sure how to do "if else" condition I need the way I explained above that is if code ==1 then pull only that document if not look if code ==2 then pull that document if not all else pull whatever is available.
I know this is complex to explain but I think I need some fix in my $filter cond.
any help would be greatly appreciated
I do not know if this will work, I also do not know what comparison you are trying to perform. If you give me that information, I will recreate the dataset and try it at my end.
db.collection.aggregate([{$match: {'_id':123}},
{$project: {
"fn" :1,
"addresses": {$cond: {
if: {$eq: ["YOUR_EXPRESSION": 1]},
then: "addresses.0.line",
else: "addresses.1.line"
}},
_id: 0
}}
I want to search a field in array and want to know what is the index of that field in array is it possible to find it?
example:
doc1: {id:123, hobby:['chess','cards','dance']}
doc2: {id:123, hobby:['cards','dance','chess']}
I want to search only the document which has
chess and cards but only the one which has chess before card. So in case doc1.
You can do this with the $where operator, coupling it with the $all operator to keep it reasonably performant:
db.test.find({
// Find all the docs where hobby contains both 'cards' and 'chess'
hobby: {$all: ['cards', 'chess']},
// Of those, run this $where query that evaluates the indexes of the matched elements
$where: "this.hobby.indexOf('cards') > this.hobby.indexOf('chess')"
})
Add an index on hobby for best performance.
For the best performance you would actually use .aggregate() with a $match stage for a "query" to narrow down possible matches to documents that contain $all the elements in the array, and $redact to filter from the remaining results:
db.test.aggregate([
{ "$match": { "hobby": { "$all": [ "chess", "cards" ] } } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [
{ "$filter": {
"input": "$hobby",
"as": "hobby",
"cond": {
"$or": [
{ "$eq": [ "$$hobby", "chess" ] },
{ "$eq": [ "$$hobby", "cards" ] }
]
}
}},
["chess","cards"]
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
The reason that works is because $filter can return only the elements that match the conditions given here, and returns them "in the same order" in which they are stored in the original document.
Once "filtered" down, the array should then be an exact match for the content ["chess","cards"] if indeed those elements in the original array were in that order.
Note that while it may "look" simpler, you cannot use "set operators" to test this since "sets" are not actually considered to be ordered.
So you need something that maintains the order, and $filter is really the only thing that can do this with array content in an efficient way. So the solution is indeed restricted to requiring a MongoDB 3.2 or greater release in order to actually work.
For older releases, see the answer from JohnnyHK using $where to apply an .indexOf() test on the array elements as the additional filter to using $all.
But if you have MongoDB 3.2 with the available operators, then the native coded operations available to the aggregation framework run much faster than the JavaScript evaluation of$where, and should be used in preference.