How do I update MongoDB document fields only if they don't exist?
I have collection foo
with documents like:
{site_id: 'xxx', title: {ru: 'a', en: 'b'}, content: {ru: 'a', en: 'b'}}
{site_id: 'xxx', title: {ru: 'c', de: 'd'}, content: {ru: 'c', de: 'd'}}
I need to update multiple fields which are can exists or not:
db.foo.update(
{ site_id: 'xxx'},
{ $set: {'title.de': '', 'content.de': ''}},
{multi: true}
)
But I need something like $set
which will not overwrite value if it exists.
Solution 1:
You can add a query to your update statement:
db.foo.update({'title.de': {$exists : false}}, {$set: {'title.de': ''}})
Update
For your modified question my solution looks like this - would that work for you? (If not, why?)
db.foo.update({site_id: 'xxx', 'title.de': {$exists : false}}, {$set: {'title.de': ''}, {multi: true})
db.foo.update({site_id: 'xxx', 'content.de': {$exists : false}}, {$set: {'content.de': ''}}, {multi: true})
Solution 2:
Starting Mongo 4.2
, db.collection.update()
can accept an aggregation pipeline, finally allowing the update/creation of a field based on another field:
This way, we can move field checks within the update stage rather than within the match stage, thus making it a one-pass update:
// { site_id: "xxx", title: { ru: "a", en: "b" }, content: {} }
// { site_id: "xxx", title: { ru: "c", de: "d" }, content: { ru: "c" } }
db.collection.update(
{ site_id: "xxx" },
[{ $set: {
"title.de": { $cond: [ { $not: ["$title.de"] }, "", "$title.de" ] },
"content.ru": { $cond: [ { $not: ["$content.ru"] }, "", "$content.ru" ] }
}}],
{ multi: true }
)
// { site_id: "xxx", title: { ru: "a", en: "b", de: "" }, content: { ru: "" } }
// { site_id: "xxx", title: { ru: "c", de: "d" }, content: { ru: "c" } }
The first part
{ site_id: "xxx" }
is the match query, filtering which documents to update.The second part
[{ $set: { ... } }]
is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline).$set
is a new aggregation operator and an alias of$addFields
. The rest of this stage checks with$cond
if thetitle.de
exists, and if yes, then keep it as it is, or otherwise create it as an''
.Don't forget
{ multi: true }
, otherwise only the first matching document will be updated.
Solution 3:
there is a update field operator $setOnInsert which meets your requirement. Please read the documents here: https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#up._S_setOnInsert
Solution 4:
I have a solution for one particular case, but may be it helps someone.
My case was: Update several fields, among which was a field that had to be updated only once (lets call it "Date_of_first_update").
> db.test.find();
{ "_id" : ObjectId("57f298fdeb30478a033c70e4"), "a" : "1", "b" : "2" }
First update:
> db.test.updateOne({ "_id" : ObjectId("57f298fdeb30478a033c70e4")},
{$set: {a: 100, b: 200 }, $min : {'Date_of_first_update' : (new Date()) }});
Result: 'a', 'b' updated, 'Date_of_first_update' is set.
{ "_id" : ObjectId("57f298fdeb30478a033c70e4"), "a" : 100, "b" : 200, "Date_of_first_update" : ISODate("2016-10-03T**17:47:43**.570Z") }
Second update:
> db.test.updateOne({ "_id" : ObjectId("57f298fdeb30478a033c70e4")},
{$set: {a: 400, b: 800 }, $min : {'Date_of_first_update' : (new Date()) }});
Result: 'a', 'b' updated, 'Date_of_first_update' left unchanged, as I needed!!!
{ "_id" : ObjectId("57f298fdeb30478a033c70e4"), "a" : 400, "b" : 800, "Date_of_first_update" : ISODate("2016-10-03T**17:47:43**.570Z") }
Solution 5:
If anyone encounters the problem as me:
My solution is to set _id only if the update results in insert a new on (upsert = true)