Search code examples
pythonmongodbpymongomongodb-update

MongoDB using $cond with Update ($inc)


Is there any way to use $cond along with ($set, $inc, ...) operators in update? (MongoDB 4.2) I want to update a field in my document by $inc it with "myDataInt" if a condition comes true, otherwise keeps it as it is:

db.mycoll.update(
    {"_id" : "5e9e5da03da783817d231dc4"},
    {"$inc" : {
       "my_data_sum" : {
           "$cond" : [
               {
                  "$ne" : ["snapshot_time", new_snapshot_time)]
               },myDataInt, 0]
           ]
       }
    },
    {upsert=True, multi=False}
)

However, this gives an error in pymongo:

raise WriteError(error.get("errmsg"), error.get("code"), error)
pymongo.errors.WriteError: The dollar ($) prefixed field '$cond' in 'my_data_sum.$cond' is not valid for storage.

Any idea to avoid using find() before update in this case?


Update:

If I use the approach that Joe has mentioned, an exception will be raised in PyMongo (v3.10.1) due to using 'list' as a parameter in update_many() instead of 'dict':

from pymongo import MongoClient

db = MongoClient()['mydb']

db.mycoll.update_many(
    {"_id" : "5e9e5da03da783817d231dc4"},
    [{"$set" : {
       "my_data_sum" : {
           "$sum": [
               "$my_data_sum",
               {"$cond" : [
                   {"$ne" : ["snapshot_time", new_snapshot_time]},
                   myDataInt, 
                   0
               ]}
           ]
       }
    }}],

    upsert:true
)


That ends up with this error:

  File "/usr/local/lib64/python3.6/site-packages/pymongo/collection.py", line 1076, in update_many session=session),
  File "/usr/local/lib64/python3.6/site-packages/pymongo/collection.py", line 856, in _update_retryable _update, session)
  File "/usr/local/lib64/python3.6/site-packages/pymongo/mongo_client.py", line 1491, in _retryable_write return self._retry_with_session(retryable, func, s, None)
  File "/usr/local/lib64/python3.6/site-packages/pymongo/mongo_client.py", line 1384, in _retry_with_session return func(session, sock_info, retryable)
  File "/usr/local/lib64/python3.6/site-packages/pymongo/collection.py", line 852, in _update retryable_write=retryable_write)
  File "/usr/local/lib64/python3.6/site-packages/pymongo/collection.py", line 823, in _update _check_write_command_response(result)
  File "/usr/local/lib64/python3.6/site-packages/pymongo/helpers.py", line 221, in _check_write_command_response _raise_last_write_error(write_errors)
  File "/usr/local/lib64/python3.6/site-packages/pymongo/helpers.py", line 203, in _raise_last_write_error raise WriteError(error.get("errmsg"), error.get("code"), error)
pymongo.errors.WriteError: Modifiers operate on fields but we found type array instead. For example: {$mod: {<field>: ...}} not {$set: [ { $set: { my_data_sum: { $sum: [ "$my_data_sum", { $cond: [ { $ne: [ "$snapshot_time", 1586910283 ] }, 1073741824, 0 ] } ] } } } ]}

Solution

  • After spending some time and searching online, I figured that the update_many(), update_one(), and update() methods of Collection object in PyMongo do not accept type list as parameters to support the new Aggregation Pipeline feature of the Update operation in MongoDB 4.2+. (At least this option is not available in PyMongo v3.10 yet.)

    However, looks like I could use the command method of the Database object in PyMongo which is an instance of the (MongoDB runCommand) and it worked just fine for me:

    from pymongo import MongoClient
    
    db = MongoClient()['mydb']
    
    result = db.command(
        {
            "update" : "mycoll",
            "updates" : [{
                "q" : {"_id" : "5e9e5da03da783817d231dc4"},
                "u" : [
                    {"$set" : {
                       "my_data_sum" : {
                           "$sum": [
                               "$my_data_sum",
                               {"$cond" : [
                                   {"$ne" : ["snapshot_time", new_snapshot_time]},
                                   myDataInt,
                                   0
                               ]}
                           ]
                       }
                    }}
                ],
                "upsert" : True,
                "multi" : True
            }],
            "ordered": False
        }
    )
    

    The command method of the database object gets a dict object of all the required commands as its first argument, and then the list of Aggregation Pipeline can be included inside the dict object (q is the update query, and the u defined the fields to be updated).

    result is a dictionary of Ack message from MongoDB which contains 'nModified', 'upserted', and 'writeErrors'.