14

I have such a schema:

doc:
{
    //Some fields
    visits:
    [
        {
            userID: Int32
            time: Int64
        }
    ]

}

I want to first check if a specific userID exists, if not, push a document with that userID and system time, else just update time value. I know neither $push nor $addToSet are not able to do that. Also using $ with upsert:true doesn't work, because of official documentation advice which says DB will use $ as field name instead of operator when trying to upsert.

Please guide me about this. Thanks

Bertrand Martel
  • 42,756
  • 16
  • 135
  • 159
Alireza Mohamadi
  • 751
  • 1
  • 6
  • 22

3 Answers3

14

You can use $addToSet to add an item to the array and $set to update an existing item in this array.

The following will add a new item to the array if the userID is not found in the array :

db.doc.update({
    visits: {
        "$not": {
            "$elemMatch": {
                "userID": 4
            }
        }
    }
}, {
    $addToSet: {
        visits: {
            "userID": 4,
            "time": 1482607614
        }
    }
}, { multi: true });

The following will update the subdocument array item if it matches the userId :

db.doc.update({ "visits.userID": 2 }, {
    $set: {
        "visits.$.time": 1482607614
    }
}, { multi: true });
Bertrand Martel
  • 42,756
  • 16
  • 135
  • 159
0
const p = await Transaction.findOneAndUpdate(
            {
              _id: data.id,
              'products.id': { $nin: [product.id] },
            },
            {
              $inc: {
                actualCost: product.mrp,
              },
              $push: {
                products: { id: product.id },
              },
            },
            { new: true }
          );

or

db.collection.aggregate([
  {
    "$match": {
      "_id": 1
    }
  },
  {
    "$match": {
      "sizes.id": {
        "$nin": [
          7
        ]
      }
    }
  },
  {
    "$set": {
      "price": 20
    }
  }
])

https://mongoplayground.net/p/BguFa6E9Tra

Rafiq
  • 8,987
  • 4
  • 35
  • 35
0

I know it's very late. But it may help others. Starting from mongo4.4, we can use $function to use a custom function to implement our own logic. Also, we can use the bulk operation to achieve this output.

Assuming the existing data is as below

{
  "_id" : ObjectId("62de4e31daa9b8acd56656ba"),
  "entrance" : "Entrance1",
  "visits" : [
    {
            "userId" : 1,
            "time" : 1658736074
    },
    {
            "userId" : 2,
            "time" : 1658736671
    }
  ]
}

Solution 1: using custom function

db.visitors.updateMany(
  {_id: ObjectId('62de4e31daa9b8acd56656ba')},
  [
    {
      $set: {
        visits: {
          $function: {
            lang: "js",
            args: ["$visits"],
            body: function(visits) {
              let v = []
              let input = {userId: 3, time: Math.floor(Date.now() / 1000)};
              if(Array.isArray(visits)) {
                v = visits.filter(x => x.userId != input.userId)
              }
              v.push(input)
              return v;
            }
          }
        }
      }
    }
  ]
)

In NodeJS, the function body should be enclosed with ` character

...
  lang: 'js',
  args: ["$visits"],
  body: `function(visits) {
    let v = []
    let input = {userId: 3, time: Math.floor(Date.now() / 1000)};
    if(Array.isArray(visits)) {
      v = visits.filter(x => x.userId != input.userId)
    }
    v.push(input)
    return v;
  }`
...

Solution 2: Using bulk operation:

Please note that the time here will be in the ISODate

var bulkOp = db.visitors.initializeOrderedBulkOp()
bulkOp.find({ _id: ObjectId('62de4e31daa9b8acd56656ba') }).updateOne({$pull: { visits: { userId: 2 }} });
bulkOp.find({ _id: ObjectId('62de4e31daa9b8acd56656ba') }).updateOne({$push: {visits: {userId: 2, time: new Date()}}})
bulkOp.execute()

Reference link

shafeeq
  • 1,499
  • 1
  • 14
  • 30