Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use $in or $nin in mongo aggregation $group $cond

I want to achieve $sum with $cond by having $or on property:

db.collectionName.aggregate(
{
   "$group": {
     "_id":'$created_at',
     "count": {"$sum": 1},
     "count_failure": {
         "$sum": {
           "$cond": [
               {
                 "$id":
                  { "$in": [ 0,100,101,102,103,104,105 ] }
               }, 
               1,
               0
              ] 
           }
         }
    }  
 }
)

But error says: Invalid operator "$id"

What's wrong with syntax? Or I am writing query wrongly.

Currently I am achieving this by:

db.collectionName.aggregate(
{
   "$group": {
     "_id":'$created_at',
     "count": {"$sum": 1},
     "count_failure": {
         "$sum": {
           "$cond": [
               {
                 "$or":[
                  { "$eq": [ "$id", 0 ] },
                  { "$eq": [ "$id", 100 ]},
                  { "$eq": [ "$id", 101 ]},
                  { "$eq": [ "$id", 102 ]},
                  { "$eq": [ "$id", 103 ]},
                  { "$eq": [ "$id", 104 ]},
                  { "$eq": [ "$id", 105 ]}
                 ]
               }, 
               1,
               0
              ] 
           }
         }
   }  
 }
)
like image 273
Somnath Muluk Avatar asked Nov 16 '15 12:11

Somnath Muluk


1 Answers

The comparison on $setIsSubset is a shorter option than the $or condition you are using, though it's still basically valid to do what you are doing.

The only catch with $setIsSubset is that each argument is an array so you need to convert the single element as a single element array. This is easy enough using $map:

db.collectionName.aggregate([
    { "$group": {
        "_id": "$createdAt",
        "count": { "$sum": 1 },
        "count_failure": {
            "$sum": {
                "$cond": [
                    { "$setIsSubset": [
                        { "$map": {
                            "input": ["A"],
                            "as": "el",
                            "in": "$id"
                        }},
                        [ 0,100,101,102,103,104,105 ],
                    ]},
                    1,
                    0
                ]
            }
        }
    }}    
])

Or if you prefer, then match the array of arguments against the singular value instead, with $anyElementTrue:

db.collectionName.aggregate([
    { "$group": {
        "_id": "$createdAt",
        "count": { "$sum": 1 },
        "count_failure": {
            "$sum": {
                "$cond": [
                    { "$anyElementTrue": { "$map": {
                        "input": [ 0,100,101,102,103,104,105 ],
                        "as": "el",
                        "in": { "$eq": [ "$$el", "$id" ] }
                    }}},
                    1,
                    0
                ]
            }
        }
    }}
])

Where the $map is rather traversing the arguments to match to the singular rather than forcing the singular into an array.

And of course since either form is essentially supplying true/false to the $cond then you can just reverse the logic with $not where required:

db.collectionName.aggregate([
    { "$group": {
        "_id": "$createdAt",
        "count": { "$sum": 1 },
        "count_failure": {
            "$sum": {
                "$cond": [
                    { "$not": [{ "$anyElementTrue": { "$map": {
                        "input": [ 0,100,101,102,103,104,105 ],
                        "as": "el",
                        "in": { "$eq": [ "$$el", "$id" ] }
                    }}}]},
                    1,
                    0
                ]
            }
        }
    }}
])

It really depends on how you look at it, but simply as supplied arguments then you don't really gain anything over the original form with $or. It might look a little cleaner and "easier to type", but typically I would not be "typing" such logic into the aggregation pipeline directly, but rather generating that part of the structure based on a plain list in the first place:

i.e

var failList = [ 0,100,101,102,103,104,105 ];

var orCondition = failList.map(function(el) { 
    return { "$eq": [ "$id", el ] }
})

And then just using the re-mapped array content in the pipeline definition:

    { "$group": {
        "_id": "$createdAt",
        "count": { "$sum": 1 },
        "count_failure": {
            "$sum": {
                "$cond": [
                    { "$or": orCondition },
                    1,
                    0
                ]
            }
        }
    }}
])

Whatever way you look at it, remember it's all just data structures and you have basic processes for manipulating. Both inside the pipeline processing and also in the pipeline construction itself.

like image 153
Blakes Seven Avatar answered Oct 14 '22 06:10

Blakes Seven