mongodbmongodb-query

Slice array in mongodb after $addToSet update


I have a document with a structure similar to this

{
    "brand": "BMW",
    "models": ["320","545"]
}

Models must be unique, and I am using the following query when adding new items,

db.cars.update(
    {brand:'BMW'},
    {
        $addToSet: {
            models: '750'
        }
    },
    {upsert:true}
);

That would give

{
    "brand": "BMW",
    "models": ["320","545","750"]
}

Question:

How can I limit the total number of items 'models' can have? Say I want to keep only the last 3 added models. So if I insert a new model '135' I would end up with

{
    "brand": "BMW",
    "models": ["545","750","135"]
}

I read about the $slice modifier however it appears to be only available when using $push and not $addToSet


Solution

  • 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.