MongoDB - Update or Insert object in array

There is a option called update documents with aggregation pipeline starting from MongoDB v4.2,

  • check condition $cond if userId in myarray.userId or not
  • if yes then $map to iterate loop of myarray array and check condition if userId match then merge with new document using $mergeObjects
  • if no then $concatArrays to concat new object and myarray
let _id = ObjectId("57315ba4846dd82425ca2408");
let uodateDoc = {
  userId: ObjectId("570ca5e48dbe673802c2d035"),
  point: 10
};

db.collection.update(
  { _id: _id },
  [{
    $set: {
      myarray: {
        $cond: [
          { $in: [updateDoc.userId, "$myarray.userId"] },
          {
            $map: {
              input: "$myarray",
              in: {
                $mergeObjects: [
                  "$$this",
                  {
                    $cond: [
                      { $eq: ["$$this.userId", updateDoc.userId] },
                      uodateDoc,
                      {}
                    ]
                  }
                ]
              }
            }
          },
          { $concatArrays: ["$myarray", [uodateDoc]] }
        ]
      }
    }
  }]
)

Playground


Try this

db.collection.update(
    { _id : ObjectId("57315ba4846dd82425ca2408")},
    { $pull: {"myarray.userId": ObjectId("570ca5e48dbe673802c2d035")}}
)
db.collection.update(
    { _id : ObjectId("57315ba4846dd82425ca2408")},
    { $push: {"myarray": {
        userId:ObjectId("570ca5e48dbe673802c2d035"),
        point: 10
    }}
)

Explination: in the first statment $pull removes the element with userId= ObjectId("570ca5e48dbe673802c2d035") from the array on the document where _id = ObjectId("57315ba4846dd82425ca2408")

In the second one $push inserts this object { userId:ObjectId("570ca5e48dbe673802c2d035"), point: 10 } in the same array.


The accepted answer by Flying Fisher is that the existing record will first be deleted, and then it will be pushed again.

A safer approach (common sense) would be to try to update the record first, and if that did not find a match, insert it, like so:

// first try to overwrite existing value
var result = db.collection.update(
   {
       _id : ObjectId("57315ba4846dd82425ca2408"),
       "myarray.userId": ObjectId("570ca5e48dbe673802c2d035")
   },
   {
       $set: {"myarray.$.point": {point: 10}}
   }
);
// you probably need to modify the following if-statement to some async callback
// checking depending on your server-side code and mongodb-driver
if(!result.nMatched)
{
    // record not found, so create a new entry
    // this can be done using $addToSet:
    db.collection.update(
        {
            _id: ObjectId("57315ba4846dd82425ca2408")
        },
        {
            $addToSet: {
                myarray: {
                    userId: ObjectId("570ca5e48dbe673802c2d035"),
                    point: 10
                }
            }
        }
    );
    // OR (the equivalent) using $push:
    db.collection.update(
        {
            _id: ObjectId("57315ba4846dd82425ca2408"),
            "myarray.userId": {$ne: ObjectId("570ca5e48dbe673802c2d035"}}
        },
        {
            $push: {
                myarray: {
                    userId: ObjectId("570ca5e48dbe673802c2d035"),
                    point: 10
                }
            }
        }
    );
}

This should also give (common sense, untested) an increase in performance, if in most cases the record already exists, only the first query will be executed.

Tags:

Mongodb