sc28
sc28

Reputation: 475

Mongodb $cond in aggregation framework

I have a collection with documents that look like the following: {

ipAddr: '1.2.3.4',
"results" : [
             {
                     "Test" : "Sight",
                     "Score" : "FAIL",
                     "Reason" : "S1002"
             },
             {
                     "Test" : "Speed",
                     "Score" : "FAIL",
                     "Reason" : "85"
             },
             {
                     "Test" : "Sound",
                     "Score" : "FAIL",
                     "Reason" : "A1001"
             }
     ],
     "finalGrade" : "FAILED"

}

Here's the aggregation query I'm trying to write, what I want to do (see commented out piece), is to create a grouped field, per ipAddr, of the 'Reason / Error' code, but only if the Reason code begins with a specific letter, and only add the code in once, I tried the following:

 db.aggregate([
   {$group: 
        {  _id: "$ipAddr", 
         attempts: {$sum:1}, 
         results: {$push: "$finalGrade"},    
        // errorCodes: {$addToSet: {$cond: ["$results.Reason": /[A|B|S|N.*/, "$results.Reason", ""]}},                                                              
        finalResult: {$last: "$finalGrade"} } 
   }
]);

Everything works, excluding the commented out 'errorCodes' line. The logic I'm attempting to create is: "Add the the errorCodes set the value of the results.Reason code IF it begins with an A, B, S, or N, otherwise there is nothing to add".

For the Record above, the errorCodes set should contain:
... errorCodes: [S1002,A1001], ...

Upvotes: 3

Views: 5078

Answers (2)

blacelle
blacelle

Reputation: 2217

As of Mongo 2.4, $regex can be used for pattern matching, but not as an expression returning a boolean, which is what's required by $cond

Then, you can either use a $match operator to use the $regex keyword:

http://mongotry.herokuapp.com/#?bookmarkId=52fb39e207fc4c02006fcfed

[
{
    "$unwind": "$results"
},
{
    "$match": {
        "results.Reason": {
            "$regex": "[SA].*"
        }
    }
},
{
    "$group": {
        "_id": "$ipAddr",
        "attempts": {
            "$sum": 1
        },
        "results": {
            "$push": "$finalGrade"
        },
        "undefined": {
            "$last": "$finalGrade"
        },
        "errorCodes": {
            "$addToSet": "$results.Reason"
        }
    }
}
]

or you can use $substr as your pattern matching is very simple http://mongotry.herokuapp.com/index.html#?bookmarkId=52fb47bc7f295802001baa38

[
{
    "$unwind": "$results"
},
{
    "$group": {
        "_id": "$ipAddr",
        "errorCodes": {
            "$addToSet": {
                "$cond": [
                    {
                        "$or": [
                            {
                                "$eq": [
                                    {
                                        "$substr": [
                                            "$results.Reason",
                                            0,
                                            1
                                        ]
                                    },
                                    "A"
                                ]
                            },
                            {
                                "$eq": [
                                    {
                                        "$substr": [
                                            "$results.Reason",
                                            0,
                                            1
                                        ]
                                    },
                                    "S"
                                ]
                            }
                        ]
                    },
                    "$results.Reason",
                    "null"
                ]
            }
        }
    }
}
]

Upvotes: 0

Asya Kamsky
Asya Kamsky

Reputation: 42352

$group cannot take conditional expressions, which is why that line is not working. $project is the phase where you can transform the original document based on $conditional expressions (among other things).

You need two steps in the aggregation pipeline before you can $group - first you need to $unwind the results array, and next you need to $match to filter out the results you don't care about.

That would do the simple thing of just throwing out the results with error codes you don't care about keeping, but it sounds like you want to count the total number of failures including all error codes, but then only add particular ones to the output array? There isn't a straight-forward way to do that, you would have to make two $group $unwind passes in the pipeline.

Something similar to this will do it:

db.aggregate([
   {$unwind : "$results"},
   {$group:
        { _id: "$ipAddr",
          attempts: {$sum:1},
          results: {$push : "$results"},
          finalGrade: {$last : "$finalGrade" } 
        } 
   },
   {$unwind: "$results"},
   {$match: {"results.Reason":/yourMatchExpression/} },
   {$group: 
        {  _id: "$ipAddr", 
         attempts: {$last:"$attempts"},    
         errorCodes: {$addToSet: "$results.Reason"},                                                              
        finalResult: {$last: "$finalGrade"}
   }
]);

If you only want to count attempts that have the matching error code then you can do that with a single $group - you will need to do $unwind, $match and $group. You could use $project with $cond as you had it, but then your array of errorCodes will have an empty string entry along with all the proper error codes.

Upvotes: 5

Related Questions