Replace single subdocument while keeping a property

I’m trying to achieve the following and looked into Updates with Aggregation Pipeline — MongoDB Manual but I can’t find out how to change a subdocument in an array, while keeping one of the properties of the subdocument.

My collection looks like this:

[{
    _id: 1,
    subdocs: [{
        name: 'subdoc1_1',
        description: 'subdoc 1.1 description',
        history: ['created', 'updated']
    }, {
        name: 'subdoc1_2',
        description: 'subdoc 1.2 description',
        history: ['created', 'updated', 'updated']
    }]
}, {
    _id: 2,
    subdocs: [{
        name: 'subdoc2_1',
        description: 'subdoc 2.1 description',
        history: ['created']
    }, {
        name: 'subdoc2_2',
        description: 'subdoc 2.2 description',
        history: ['created']
    }]
}, {
    ...
}]

I want to replace the e.g. subdocument with the name “subdoc2_2” with a new subdocument:

{
    name: ‘subdoc2_2’,
    description: ‘subdoc 2.2 updated description’
}

… but keep the “history” property of the original subdocument.

I’m using the Node.js native driver and tried something like this:

db.mycollection.updateOne({
    'subdocs.name': 'subdoc2_2'
}, [{
    $set: {
            'subdocs.$': {  <-- can't use a positional path here
                name: ‘subdoc2_2’,
                description: ‘subdoc 2.2 updated description’
                ? <-- I want to keep the original "history" property
            }
...

Is there a way to do this in one query, either with the newer aggregation pipelines in update (or without)?

Thank you!

Hi @phe ,

You should use array filters for that :

Follow the example for your use.

db.collection.update(
   {
    'subdocs.name': 'subdoc2_2'
},
   { $set: { "subdocs.$[t].description": 'subdoc 2.2 updated ' } },
   { arrayFilters: [ { "t.name": "subdoc2_2" }  ]}
)

Thanks Pavel.

Hi Pavel,

thanks for your quick reply.

Is there a way to not have to define every property to be updated (like subdocs.$[t].description in your example)?

My real documents have many more properties and if I do it like in your example, I have to define every single one. Is there a way to just take a new subdocument (with all the properties it has), replace the existing one, but read one existing property from the original before replacing and add it to the new one?

Thank you!

Hi @phe ,

I am not aware of other tricks in this case maybe games with $map in aggregation pipeline update.

However, doing this change programmatically in building a statement with object.keys should not be that hard and will make your code cleaner.

Thanks
Pavel