How can I use $in (like) operator inside $cond? For example, inside a "project" stage I want to create a new field called "category" that is conditional on some other fields (for grouping later): it's value would be catA if "test_value" is in the list [1,9] else "catB". For small list I can use set of $eq inside "$or".
"category": {
"$cond": {
"if" :{
"$or" : [
{ "$eq" : ["$test_value", 1]},
{"$eq" : ["$test_value", 9] }
]
},
"then": {
"$literal" : "catA"
},
"else": {
"$literal" : "catB"
}
}
Is there any way to use something like:
"if" :{
"$in" : {"$test_value", [1,9]}
},
"then": {
"$literal" : "catA"
},
"else": {
"$literal" : "catB"
}
}
I am using mongoengine, and above syntax is not allowed.
You can do intersection of the field and the list of expected values for that field using $setIntersection and check the length of the result. If the length is bigger that 0 then the field has value from the list of expected values.
Try the following query:
db.collection.aggregate([
{
$project: {
category: {
$let: {
vars: {
resultSize: {
$size: { $setIntersection: [["$test_value"], [1, 9]] }
}
},
in: {
"$cond": {
"if": { "$gt" : ["$$resultSize", 0] },
"then": { "$literal" : "catA" },
"else": { "$literal" : "catB" }
}
}
}
}
}
}
])
Related
I am trying to generate a new collection with a field 'desc' having into account a condition in field in a documment array. To do so, I am using $cond statement
The origin collection example is the next one:
{
"_id" : ObjectId("5e8ef9a23e4f255bb41b9b40"),
"Brand" : {
"models" : [
{
"name" : "AA"
},
{
"name" : "BB"
}
]
}
}
{
"_id" : ObjectId("5e8ef9a83e4f255bb41b9b41"),
"Brand" : {
"models" : [
{
"name" : "AG"
},
{
"name" : "AA"
}
]
}
}
The query is the next:
db.runCommand({
aggregate: 'cars',
'pipeline': [
{
'$project': {
'desc': {
'$cond': {
if: {
$in: ['$Brand.models.name',['BB','TC','TS']]
},
then: 'Good',
else: 'Bad'
}
}
}
},
{
'$project': {
'desc': 1
}
},
{
$out: 'cars_stg'
}
],
'allowDiskUse': true,
})
The problem is that the $cond statement is always returning the "else" value. I also have tried $or statement with $eq or the $and with $ne, but is always returning "else".
What am I doing wrong, or how should I fix this?
Thanks
Since $Brand.models.name returns an array, we cannot use $in operator.
Instead, we can use $setIntersection which returns an array that contains the elements that appear in every input array
db.cars.aggregate([
{
"$project": {
"desc": {
"$cond": [
{
$gt: [
{
$size: {
$setIntersection: [
"$Brand.models.name",
[
"BB",
"TC",
"TS"
]
]
}
},
0
]
},
"Good",
"Bad"
]
}
}
},
{
"$project": {
"desc": 1
}
},
{
$out: 'cars_stg'
}
])
MongoPlayground | Alternative $reduce
I have a collection "structure" with a document :
{
"case" : {
"version" : 3,
"key" : 1
},
"salle" : {
"version" : 2,
"key" : 1
}
}
And I try a $cond aggregation with $ifNull operator :
db.structure.aggregate([
{
"structure" : {
"$cond": {
"if": {
"$ifNull": ["$salle", true]
},
"then": "$case.key",
"else": "$salle.key"
}
}
}
])
The trouble is that $ifNull always return true and displays $case.key, while $salle is not null
The most surprising is that I get the right response with $not operator :
db.structure.aggregate([
{
"structure" : {
"$cond": {
"if": {
"$not": {
"$ifNull": ["$salle", false]
}
},
"then": "$case.key",
"else": "$salle.key"
}
}
}
])
Can anyone can explain me what's going on ?
I am trying to update my mongo database which has following structure.
{
"_id" : ObjectId("5a64d076bfd103df081967ae"),
"values" : [
{
"date" : "2018-01-22",
"Price" : "1289.4075"
},
{
"date" : "2018-01-22",
"Price" : "1289.4075"
},
{
"date" : "2015-05-18",
"Price" : 1289.41
}
],
"Code" : 123456,
"schemeStatus" : "Inactive"
}
I want to compare first 2 array element's date value i.e values[0].date and values[1].date. If both matches then I want to delete values[0] so that there will be only 1 entry with that date.
You can use aggregation framework's pipeline with $out as a last stage to update your collection
db.collection.aggregate([
{
$addFields: {
sameDate: {
$let: {
vars: {
fst: { $arrayElemAt: [ "$values", 0 ] },
snd: { $arrayElemAt: [ "$values", 1 ] }
},
in: { $cond: { if: { $eq: [ "$$fst.date", "$$snd.date" ] }, then: 1, else: 0 } }
}
}
}
},
{
$project: {
_id: 1,
values : { $cond: { if: { $eq: [ "$sameDate", 0 ] }, then: "$values", else: { $slice: [ "$values", 1, { $size: "$values" } ] } } },
Code: 1,
schemeStatus: 1
}
},
{ $out: "collection" }
])
Some more important operators used here:
$cond to handle if-else logic
$let to define some helper variables
$arrayElemAt to get first and second element
$slice to pop first element
The following query is not valid. Can anyone please point me to the error?
I am trying to filter on all documents where the last item in the Progress array has a given state - for example "Done".
db.logdata.aggregate(
[
{ "$match": { "ProcessingInfo.Progress" : { "$exists": true } } },
{ "$redact":
{
"$cond": {
"if": { "$eq": [ { "$arrayElemAt": [ "$ProcessingInfo.Progress.State", -1 ], "Done" } ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}
}
]
)
Sample document (which should be matched - because the last State in Progress array is "InProgress"):
{
"_id" : ObjectId("578fa85bb29339a1fa6d6109"),
"ProcessingInfo" : {
"DateStarted" : ISODate("2016-08-06T16:55:58.294+0000"),
"Attempt" : NumberInt(1),
"LastState" : "Failed",
"Progress" : [
{
"State" : "Failed",
"StateDescription" : ""
},
{
"State" : "Success",
"StateDescription" : ""
},
{
"State" : "Done",
"StateDescription" : ""
},
{
"State" : "InProgress",
"StateDescription" : ""
}
]
}
}
To kind of "circumvent" this problem I have an extra field in the document root "LastState" - is that maybe the way to go (complexity-wise)?
Your query has a little syntax error: your "Done" should be in the $eq array, not in the object containing $arrayElemAt definition. The following query (optimised to filter out documents without the expected State anywhere in the ProcessInfo.Progress array thanks to what Styvane suggested) should return your example document:
[
{ "$match": { "ProcessingInfo.Progress.State" : "InProgress" } },
{ "$redact":
{
"$cond": {
"if": { "$eq": [ { "$arrayElemAt": [ "$ProcessingInfo.Progress.State", -1 ] }, "InProgress" ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}
}
]
I have collection in mongodb (3.0):
{
_id: 1,
m: [{_id:11, _t: 'type1'},
{_id:12, _t: 'type2'},
{_id:13, _t: 'type3'}]
},
{
_id: 2,
m: [{_id:21, _t: 'type1'},
{_id:22, _t: 'type21'},
{_id:23, _t: 'type3'}]
}
I want to find documents with m attributes where m._t containing ['type1', 'type2'].
Like this:
{
_id: 1,
m: [{_id:11, _t: 'type1'},
{_id:12, _t: 'type2'}]
},
{
_id: 2,
m: [{_id:21, _t: 'type1'}]
}
I tried to use $ and $elemMatch, but couldn't get required result.
How to do it, using find()?
Help me, please! Thanks!
Because the $elemMatch operator limits the contents of the m array field from the query results to contain only the first element matching the $elemMatch condition, the following will only return the an array with the first matching elements
{
"_id" : 11,
"_t" : "type1"
}
and
{
"_id" : 21,
"_t" : "type1"
}
Query using $elemMatch projection:
db.collection.find(
{
"m._t": {
"$in": ["type1", "type2"]
}
},
{
"m": {
"$elemMatch": {
"_t": {
"$in": ["type1", "type2"]
}
}
}
}
)
Result:
/* 0 */
{
"_id" : 1,
"m" : [
{
"_id" : 11,
"_t" : "type1"
}
]
}
/* 1 */
{
"_id" : 2,
"m" : [
{
"_id" : 21,
"_t" : "type1"
}
]
}
One approach you can take is the aggregation framework, where your pipeline would consist of a $match operator, similar to the find query above to filter the initial stream of documents. The next pipeline step would be the crucial $unwind operator that "splits" the array elements to be further streamlined with another $match operator and then the final $group pipeline to restore the original data structure by using the accumulator operator $push.
The following illustrates this path:
db.collection.aggregate([
{
"$match": {
"m._t": {
"$in": ["type1", "type2"]
}
}
},
{
"$unwind": "$m"
},
{
"$match": {
"m._t": {
"$in": ["type1", "type2"]
}
}
},
{
"$group": {
"_id": "$_id",
"m": {
"$push": "$m"
}
}
}
])
Sample Output:
/* 0 */
{
"result" : [
{
"_id" : 2,
"m" : [
{
"_id" : 21,
"_t" : "type1"
}
]
},
{
"_id" : 1,
"m" : [
{
"_id" : 11,
"_t" : "type1"
},
{
"_id" : 12,
"_t" : "type2"
}
]
}
],
"ok" : 1
}
To get your "filtered" result, the $redact with the aggregation pipeline is the fastest way:
db.junk.aggregate([
{ "$match": { "m._t": { "$in": ["type1", "type2"] } } },
{ "$redact": {
"$cond": {
"if": {
"$or": [
{ "$eq": [ { "$ifNull": ["$_t", "type1"] }, "type1" ] },
{ "$eq": [ { "$ifNull": ["$_t", "type2"] }, "type2" ] }
],
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
The $redact operator sets up a logical filter for the document that can also traverse into the array levels. Note that this is matching on _t at all levels of the document, so make sure there are no other elements sharing this name.
The query uses $in for selection just as the logical filter uses $or. Anything that does not match, gets "pruned".
{
"_id" : 1,
"m" : [
{
"_id" : 11,
"_t" : "type1"
},
{
"_id" : 12,
"_t" : "type2"
}
]
}
{
"_id" : 2,
"m" : [ { "_id" : 21, "_t" : "type1" } ]
}
Short and sweet and simple.
A bit more cumbersome, but a reasonably safer is to use this construct with $map and $setDifference to filter results:
db.junk.aggregate([
{ "$match": { "m._t": { "$in": ["type1", "type2"] } } },
{ "$project": {
"m": {
"$setDifference": [
{ "$map": {
"input": "$m",
"as": "el",
"in": {
"$cond": {
"if": {
"$or": [
{ "$eq": [ "$$el._t", "type1" ] },
{ "$eq": [ "$$el._t", "type2" ] }
]
},
"then": "$$el",
"else": false
}
}
}},
[false]
]
}
}}
])
The $map evaluates the conditions against each element and the $setDifference removes any of those condtions that returned false rather than the array content. Very similar to the $cond in redact above, but it is just working specifically with the one array and not the whole document.
In future MongoDB releases ( currently available in development releases ) there will be the $filter operator, which is very simple to follow:
db.junk.aggregate([
{ "$match": { "m._t": { "$in": ["type1", "type2"] } } },
{ "$project": {
"m": {
"$filter": {
"input": "$m",
"as": "el",
"cond": {
"$or": [
{ "$eq": [ "$$el._t", "type1" ] },
{ "$eq": [ "$$el._t", "type2" ] }
]
}
}
}
}}
])
And that will simply remove any array element that does not match the specified conditions.
If you want to filter array content on the server, the aggregation framework is the way to do it.