0

I'm trying to update my collection

For the single, object need to update the status and updated time.

Input object id as id new status as current status

Condition If the new status is different from the status in DB, then need to $push new status and timestamp into activity_log array. And update the update the updated_time and status of the record.

If the new status and previous status is same then updated_time will be updated.

Is it possible to do that in pymongo using single update ?

collection.update({
        '_id': ObjectId(id),
    }, {
        '$cond': {
            'if': {
                'status': {
                    '$ne': current_status
                }
            },
            'then': {
                '$push': {
                    'activity_log': {
                        'status': current_status,
                        'changed_time': datetime.now()
                    }
                }
            },
            'else': None
        },
        '$set': {
            'status': current_status,
            'updated_time': datetime.now()
        }
    })
wulfnb
  • 111
  • 1
  • 11

2 Answers2

2

You can do that in a single query, From MongoDB 4.2 update query supports aggregation pipeline, You can do that as per below code

collection.update(
     {'_id': ObjectId(id)}, 
     [{'$set': {
            'status': current_status,
            'updated_time': datetime.now(),
            'activity_log':{
              '$cond': {
                'if': {'$ne': ['$status',current_status]},
                'then': {$concatArrays:['$activity_log',[
                          {
                           'status': current_status,
                           'changed_time': datetime.now()
                          }
                ]]},
                'else': '$activity_log'
            }
      }}}]
)
Puneet Singh
  • 3,477
  • 1
  • 26
  • 39
1

The $cond operator is not flow control like if-then-else, it is an expression that returns a value.

If you want to conditionally update a field based on the value of another field in the same document, you can use the pipeline form of update, assigning the field the result of the $cond expression.

collection.update(
     {'_id': ObjectId(id)}, 
     [{'$set': {
            'status': current_status,
            'updated_time': datetime.now(),
            'activity_log':{
              '$cond': {
                'if': {'$ne': ['$status',current_status]}
                'then': {$concatArrays:['$activity_log',[
                          {
                           'status': current_status,
                           'changed_time': datetime.now()
                          }
                ]]},
                'else': '$activity_log'
            }
      }}]
)
Joe
  • 25,000
  • 3
  • 22
  • 44
  • I think the above way is correct pymongo is triggering ```in validate_is_mapping raise TypeError("%s must be an instance of dict, bson.son.SON, or " TypeError: document must be an instance of dict, bson.son.SON, or any other type that inherits from collections.Mapping``` – wulfnb May 08 '20 at 15:27
  • The pipeline form of update as added in MongoDB 4.2. If the version of pymongo you are using doesn't support that in the `update` function, you might be able to run the [update](https://docs.mongodb.com/manual/reference/command/update/index.html#update) database command using [db.command](https://stackoverflow.com/a/27298423/2282634) – Joe May 08 '20 at 16:37