27

I was looking to pull some value from array and simultaneously trying to update it.

    userSchema.statics.experience = function (id,xper,delet,callback) {
    var update = {
      $pull:{
        'profile.experience' : delet
      },

        $push: {
          'profile.experience': xper
        }
  };
  this.findByIdAndUpdate(id,update,{ 'new': true},function(err,doc) {
    if (err) {
      callback(err);
    } else if(doc){
      callback(null,doc);
    }
  });
};

i was getting error like:

MongoError: exception: Cannot update 'profile.experience' and 'profile.experience' at the same time

turivishal
  • 34,368
  • 7
  • 36
  • 59
santhosh
  • 1,919
  • 3
  • 21
  • 33

4 Answers4

25

I found this explanation:

The issue is that MongoDB doesn’t allow multiple operations on the same property in the same update call. This means that the two operations must happen in two individually atomic operations.

And you can read that posts:

Pull and addtoset at the same time with mongo

multiple mongo update operator in a single statement?

Community
  • 1
  • 1
Vladislav Kievski
  • 1,637
  • 11
  • 12
15

In case you need replace one array value to another, you can use arrayFilters for update.

(at least, present in mongo 4.2.1).

db.your_collection.update(
    { "_id": ObjectId("your_24_byte_length_id") },
    { "$set": { "profile.experience.$[elem]": "new_value" } },
    { "arrayFilters": [ { "elem": { "$eq": "old_value" } } ], "multi": true }
) 

This will replace all "old_value" array elements with "new_value".

turivishal
  • 34,368
  • 7
  • 36
  • 59
Nikolay Prokopyev
  • 1,260
  • 12
  • 22
3

Starting from MongoDB 4.2

You can try to update the array using an aggregation pipeline.

this.updateOne(
  { _id: id }, 
  [
    {
      $set: {
        "profile.experience": {
          $concatArrays: [
            {
              $filter: {
                input: "$profile.experience",
                cond: { $ne: ["$$this", delet] },
              },
            },
            [xper],
          ],
        },
      },
    },
  ]
);

Following, a mongoplayground doing the work:

https://mongoplayground.net/p/m1C1LnHc0Ge

OBS: With mongo regular update query it is not possible.

Gabriel Lupu
  • 1,397
  • 1
  • 13
  • 29
igortp
  • 179
  • 1
  • 14
0

Since Mongo 4.2 findAndModify supports aggregation pipeline which will allow atomically moving elements between arrays within the same document. findAndModify also allows you to return the modified document (necessary to see which array elements were actually moved around).

The following includes examples of:

  1. moving all elements from one array onto the end of a different array
  2. "pop" one element of an array and "push" it to another array

To run the examples, you will need the following data:

db.test.insertMany( [
   {
    "_id": ObjectId("6d792d6a756963792d696441"),
    "A": [ "8", "9" ],
    "B": [ "7" ]
  },
  {
    "_id": ObjectId("6d792d6a756963792d696442"),
    "A": [ "1", "2", "3", "4" ],
    "B": [ ]
  }
]);

Example 1 - Empty array A by moving it into array B:

db.test.findAndModify({
    query: { _id: ObjectId("6d792d6a756963792d696441") },
    update: [
      { $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, "$A" ] } } },
      { $set: { "A": [] } }
    ],
    new: true
});

Resulting in:

{
  "_id": {
    "$oid": "6d792d6a756963792d696441"
  },
  "A": [],
  "B": [
    "7",
    "8",
    "9"
  ]
}

Example 2.a - Pop element from array A and push it onto array B

db.test.findAndModify({
    query: { _id: ObjectId("6d792d6a756963792d696442"),
            "A": {$exists: true, $type: "array", $ne: [] }},
    update: [
      { $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ { $first: "$A" } ] ] } } },
      { $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
    ],
    new: true
});

Resulting in:

{
  "_id": {
    "$oid": "6d792d6a756963792d696442"
  },
  "A": [
    "2",
    "3",
    "4"
  ],
  "B": [
    "1"
  ]
}

Example 2.b - Pop element from array A and push it onto array B but in two steps with a temporary placeholder:

db.test.findAndModify({
    query: { _id: ObjectId("6d792d6a756963792d696442"),
             "temp": { $exists: false } },
    update: [
      { $set: { "temp": { $first: "$A" } } },
      { $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
    ],
    new: true
});

// do what you need to do with "temp"

db.test.findAndModify({
    query: { _id: ObjectId("6d792d6a756963792d696442"),
             "temp": { $exists: true } },
    update: [
      { $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ "$temp" ] ] } } },
      { $unset: "temp" }
    ],
    new: true
});
Jacob
  • 974
  • 1
  • 12
  • 21