Slice array in mongodb after $addToSet update

Interesting question as this is actually the subject of a bug that has been raised an an actual request for addition. As for the request, I wouldn't hold your breath on getting this behavior added.

The bug is in that you could actually issue the following:

db.cars.update(
    {brand:'BMW'},
    { $addToSet: { models: { $each: ['200'], $slice: 3 } } },
    {upsert: true}
)

and addToSet would simply work whilst ignoring the modifier, or warning. But of course you would now have four elements in your array.

The comments, from the CTO no less, point to a conflict in terms.

Thus the only thing I can think of is to first test for the element in the array using $elemMatch in a find and if both the document exists and the $elemMatch find is not true then update with a regular $push and slice. Otherwise insert the new document or leave it alone

db.cars.update(
   {brand:'BMW'},
   {$push: { models: { $each: ['750'], $slice: -3 } }}
)

That would result in two more ops over the wire. Alternately pull down the document and manipulate the array yourself which would be one more op. Either way you loose the upsert magic and have to do things manually.


Had the same problem. Here is the workaround that I did (works more efficient with bulk operations obviously, but can work on single operations also):

Basically you add dummy empty $push operator and use $slice on it. But since $addToSet is NOT guarantee order, we also need to add $sort on it.

As far as I understand, you need the model names in the order they inserted. So you will need to add a timestamp to it (we can use epoch). So we convert the model to an object: { name: '750', ts: 1552428379 }

bulk = [
  {
    updateOne: {
      filter: { brand: 'BMW' },
      update: {
        $addToSet: {
          models: { name: '750', ts: 1552428379 }
        }
      },
      upsert: true
    }
  },
  {
    updateOne: {
      filter: { brand: 'BMW' },
      update: {
        $push: {
          models: {
            $each: [],
            $sort: { ts: 1 },
            $slice: -3
          }
        }
      }
    }
  },
]
db. cars.bulkWrite(bulk)

FYI, it looks like you can't use $addToSet and $push in the same operation. That is why we use 2 updates.

IMPORTANT: make sure you are not using 'ordered: false' in the bulk operation!