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.
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})
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
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") }
Even though the answer given essentially outlines the approach, you can do this sort of thing with MongoDB 2.6 or greater due to the implementation there that supports "bulk updates".
This are still, "atomically speaking", separate update statements. But you can submit them "over the wire" in one go. Which at least makes sure that the latency between the updates is much shorter as they are executed on the server:
var bulk = db.foo.initializeBulkOrderedOp();
bulk.find({ "site_id": "xxx",
"title.de": { "$exists" false } })
.update({ "$set": { "title.de": "" } });
bulk.find({ "site_id": "xxx",
"content.de": { "$exists" false } })
.update({ "$set": { "content.de": "" } });
bulk.execute();
So that is actually one round trip to the server as everything only sends on .execute()
But in your present form (though this may not be a accurate representation of your data), you can actually "re-structure" in order to do this in a single operation. So if your documents looked like this:
{
"site_id": "xxx",
"docs": [
{ "title": "a", "content": "a", "lang": "ru" },
{ "title": "b", "content": "b", "lang": "en" }
]
},
{
"site_id": "xxx",
"docs": [
{ "title": "c", "content": "c", "lang": "ru" },
{ "title": "d", "content": "d", "lang": "de" }
]
}
Then the following works by the rule of $addToSet
where the "set" element would be "unique":
db.foo.update(
{ "site_id": "xxx" },
{ "$addToSet": { "docs": { "title": "d", content: "d", "lang": "de" } } },
{ "multi": true }
)
Or even without the logic there and just checking for presence:
db.foo.update(
{ "site_id": "xxx", "docs.lang": { "$ne": "de" } },
{ "$push": { "docs": { "title": "", "content": "", "lang": "de" } } },
{ "multi": true }
)
Which in that last case would result in this:
{
"_id" : ObjectId("53c936265117367f5ff2038b"),
"site_id" : "xxx",
"docs" : [
{
"title" : "a",
"content" : "a",
"lang" : "ru"
},
{
"title" : "b",
"content" : "b",
"lang" : "en"
},
{
"title" : "",
"content" : "",
"lang" : "de"
}
]
}
{
"_id" : ObjectId("53c936265117367f5ff2038c"),
"site_id" : "xxx",
"docs" : [
{
"title" : "c",
"content" : "c",
"lang" : "ru"
},
{
"title" : "d",
"content" : "d",
"lang" : "de"
}
]
}
So the choice is there to either "handle" things differently or otherwise just change your schema to accommodate the sort of updates you want to do atomically.
@nutlike's answer does solve the issue, however, if you would like to update multiple fields on the item it will require many database operations. In short, what you want is not exactly possible.
If your doc has more updates to do than you'd like to do one at a time (any greater than 2, IMO), then you should just get the document, update the fields and then save it. This is what I do on a couple of OAuth user creating/updating routes.