Home > other >  MongoDB update array if condition matches
MongoDB update array if condition matches

Time:03-31

I'm looking for a MongoDB aggregation pipeline which updates an array with a conditional statement. My data looks like the following:

{
    "_id": 1,
    "locations": [
      {
        "controllerID": 1,
        "timestamp": 1234
      },
      {
        "controllerID": 2,
        "timestamp": 2342
      }
    ]
  },...

Potential new entry:

{
 "controllerID": 2,
 "timestamp": //will be set automatically
}

At first I want to match the _id (not a problem) and then push the new entry to the locations array if the element with the newest/latest timestamp has a different controllerID. When pushing a new location object the timestamp will be set automatically.

Example 1

Input:

{
 "controllerID": 2,
}

Expected Result:

{
    "_id": 1,
    "locations": [
      {
        "controllerID": 1,
        "timestamp": 1234
      },
      {
        "controllerID": 2,
        "timestamp": 2342
      }//noting is added because the newset entry in the array has the same controllerID
    ]
  },

Example 2

Input:

{
 "controllerID": 1,
}

Expected Result:

 {
        "_id": 1,
        "locations": [
          {
            "controllerID": 1,
            "timestamp": 1234
          },
          {
            "controllerID": 2,
            "timestamp": 2342
          },
          {//added because the controllerID is different to te last element
            "controllerID": 1, 
            "timestamp": 4356
          }
        ]
      },

Thanks in advance!

CodePudding user response:

Here's a solution.

var candidate = 2;

rc=db.foo.update({}, // add matching criteria here; for now, match ALL                                
         [ 
         [
             // We cannot say "if condition then set fld = X else do nothing".    
             // We must say "set fld to something based on condition."            
             // The common pattern becomes:                                       
             //   "Set fld to (if condition then X else fld)"                     
             // in other words, set the fld to *itself* 
             //
             // Note the use of the dot operator on the $locations field.
             // Also, not sure about what sort of new timestamp is desired so let's
             // just throw in an ISODate() for now.                          
             {$set: {'locations': {$cond: [
                 {$ne:[candidate, {$last:'$locations.controllerID'}]}, // IF not same as candidate...                                                                             
                 {$concatArrays: ['$locations',
// $concatArrays wants arrays, not objects, so we must wrap our new
// object with [] to make an array of 1:
                                  [ {controllerId:candidate,timestamp:new ISODate() } ]
                                 ]}, // THEN concat a new entry to end of existing locations                                                                       
                 '$locations' // ELSE just set back to existing locations         
                 ]}
             }}
         ],
         {multi:true}
);

The engine is "smart enough" to realize that setting a field to itself will not trigger a modification so the approach is performant and will not rewrite the entire set of matched objects; this can be seen in the output of the update() call, e.g.:

printjson(rc);
{ "nMatched" : 1002, "nUpserted" : 0, "nModified" : 1 }
  • Related