Can mongo upsert array data?

Solution 1:

I'm not aware of an option that would upsert into an embedded array as at MongoDB 2.2, so you will likely have to handle this in your application code.

Given that you want to treat the embedded array as sort of a virtual collection, you may want to consider modelling the array as a separate collection instead.

You can't do an upsert based on a field value within an embedded array, but you could use $addToSet to insert an embedded document if it doesn't exist already:

db.soup.update({
    "tester":"tom"
}, {
    $addToSet: {
        'array': {
            "id": "3",
            "letter": "d"
        }
    }
})

That doesn't fit your exact use case of matching by id of the array element, but may be useful if you know the expected current value.

Solution 2:

I just ran into this problem myself. I wasn't able to find a one-call solution, but I found a two-call solution that works when you have a unique value in your array elements. Use the $pull command first, which removes elements from an array, and then $push.

db.soup.update({
    "tester":"tom"
}, {
    $pull: {
        'array': {
            "id": "3"
        }
    }
})
db.soup.update({
    "tester":"tom"
}, {
    $push: {
        'array': {
            "id": "3",
            "letter": "d"
        }
    }
})

This should work when the document doesn't exist, when the document exists but the entry in the array doesn't exist, and when the entry exists.

Again, this only works if you have something, like the id field in this example, that should be unique across elements of the array.

Solution 3:

You can achieve upserts of embedded document sets in some cases if you can restructure your documents to use objects instead of arrays. I just answered this on another question but will adapt it here for convenience.


An example document would be

{
    tester: 'tom',
    items: {
        '1': 'a',
        '2': 'b'
    }
}

To upsert an item with id 3 and value 'c', you'd do

db.coll.update(
    { tester: 'tom' }, 
    { $set: { 'items.3': 'c' } }
)

One drawback is that querying for field names (using the $exists query operator) requires scanning. You can get around this by adding an extra array field that stores the property names. This field can be indexed and queried normally. Upserts become

db.coll.update(
    { tester: 'tom' }, 
    { 
        $set: { 'items.3': 'c' },
        $addToSet: { itemNames: 3 }
    }
)

(Keep in mind $addToSet is O(n).) When removing a property, you have to pull it from the array as well.

db.widgets.update(
    { tester: 'tom' }, 
    { 
        $unset: { 'items.3': true },
        $pull: { itemNames: 3 }
    }
)

Solution 4:

The question is from 2012. I had a look into the mongo documentation: https://docs.mongodb.com/manual/reference/method/Bulk.find.upsert which Rob Watts mentioned in a comment, but it is very sparse.

I found an answer here: https://stackoverflow.com/a/42952359/1374488