mongodb upsert with condition - mongodb

is there a way with mongodb to bulk upsert but choose what to do when inserting and when updating.
for example:
I have this document in the mongodb:
{name:"david", last_name:"family_name", published:true}
now arrives a new document that is like this:
{name:"david", last_name:"family_name_changed", published:false}
now I want to replace the document that is in the DB (document 1)
that will only update the last_name field (or any other field) but I dont want it to update the published field. (ever)
what is the way to do that?
thank you

You can use $set to update a specific field, for instance:
db.<name of your collection>.update(<query>,
{
$set:
{
last_name: <value to set>
}
}, { multi: true } );

Related

Native way to add createdAt in MongoDB

Question in short: Is there anyway to automatically add createdAt field to each document inserted in a collection in MongoDB?
Scenario: I'm using a dockerized instance of MongoDB and I want to automatically add createdAt field to every document on insertion. I want to achieve this using MongoDB itself because the database can have multiple input channels like Logstash, Python code, ..etc.
Note: I should be able to execute JavaScript code using init file in docker-entrypoint-initdb.d.
Is there a hook or something I can use to automatically achieve this ?
Natively the createdAt timestamp is contained inside the first 4 bytes in the default mongoDB _id field in newly inserted document , so generally you dont need one more field that will contain same data:
> db.yourCollection.findOne()._id.getTimestamp()
ISODate("2022-05-05T02:57:31Z")
But modern frameworks add this option so for example in node.js you can add something like this:
var yourSchema = new Schema({..});
yourSchema.set('timestamps', true);
which will add the "createdAt" and "updatedAt" fields to the document automatically on insert or update operations via the framework...
or if you need only createdAt:
var yourSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } });
There is also an option called $setOnInsert where you can add the createdAt during upsert operation:
db.yourCollection.updateOne(
{ _id: 1 },
{
$set: { item: "Some item" },
$setOnInsert: { createdAt: new Date() }
},
{ upsert: true }
)

Why is my MongoDb query inserting an embedded document on Update?

This is my MongoDB query:
db.events.update({date:{$gte: ISODate("2014-09-01T00:00:00Z")}},{$set:{"artists.$.soundcloud_toggle":false}},{multi:true,upsert:false})
Apparently I cannot use "artists.$.soundcloud_toggle" to update all artist documents within the artists array:
"The $ operator can update the first array element that matches
multiple query criteria specified with the $elemMatch() operator.
http://docs.mongodb.org/manual/reference/operator/update/positional/"
I'm happy to run the query a number of times changing the index of the array in order to set the soundcloud_toggle property of every artist in every event that matches the query e.g
artists.0.soundcloud_toggle
artists.1.soundcloud_toggle
artists.2.soundcloud_toggle
artists.3.soundcloud_toggle
The problem is: when there is say, only one artist document in the artists array and I run the query with "artists.1.soundcloud_toggle" It will insert an artist document into the artist array with a single property:
{
"soundcloud_toggle" : true
},
(I have declared "upsert:false", which should be false by default anyways)
How do I stop the query from inserting a document and setting soundcloud_toggle:false when there is no existing document there? I only want it to update the property if an artist exists at the given artists array index.
If, like you said, you don't mind completing the operation with multiple queries, you can add an $exists condition to your filter.
E.g. in the 5th iteration, when updating index=4, add: "artists.4": {$exists: true}, like:
db.events.update(
{ date: {$gte: ISODate("2014-09-01T00:00:00Z")},
"artists.4": {$exists: true} },
{ $set:{ "artists.4.soundcloud_toggle" :false } },
{ multi: true, upsert: false }
)

Generating a new Mongo object id for each document in a collection?

How do you go about creating a unique object id for each document in a mass update?
I've tried,
db.foo.update({ objectId: null }, { $set: { objectId: new ObjectId() }}, { multi: true })
But that gives me the same object id for each document.
Because you're applying a unique value to each doc, you need to iterate over your collection and update the docs one at a time.
In the shell:
db.foo.find({ objectId: null }).forEach(function(doc) {
doc.objectId = new ObjectId();
db.foo.save(doc);
});
You cannot update multiple documents in a single command in MongoDb currently. You can update multiple documents with a common set of changes/updates, but you can not make unique changes to each document. You would need to create a loop to iterate over each document in the shell or your favorite programming language.
You can generate an object Id in MongoDB like this. You can iterate over your doc and use this mongo object id.
const ObjectId = require("mongodb").ObjectID;
const id = new ObjectId
console.log(id)

MongoDB: update every document on one field

I have a collected named foo hypothetically.
Each instance of foo has a field called lastLookedAt which is a UNIX timestamp since epoch. I'd like to be able to go through the MongoDB client and set that timestamp for all existing documents (about 20,000 of them) to the current timestamp.
What's the best way of handling this?
Regardless of the version, for your example, the <update> is:
{ $set: { lastLookedAt: Date.now() / 1000 } }
However, depending on your version of MongoDB, the query will look different. Regardless of version, the key is that the empty condition {} will match any document. In the Mongo shell, or with any MongoDB client:
$version >= 3.2:
db.foo.updateMany( {}, <update> )
{} is the condition (the empty condition matches any document)
3.2 > $version >= 2.2:
db.foo.update( {}, <update>, { multi: true } )
{} is the condition (the empty condition matches any document)
{multi: true} is the "update multiple documents" option
$version < 2.2:
db.foo.update( {}, <update>, false, true )
{} is the condition (the empty condition matches any document)
false is for the "upsert" parameter
true is for the "multi" parameter (update multiple records)
This code will be helpful for you
Model.update({
'type': "newuser"
}, {
$set: {
email: "abc#gmail.com",
phoneNumber:"0123456789"
}
}, {
multi: true
},
function(err, result) {
console.log(result);
console.log(err);
})
I have been using MongoDB .NET driver for a little over a month now. If I were to do it using .NET driver, I would use Update method on the collection object. First, I will construct a query that will get me all the documents I am interested in and do an Update on the fields I want to change. Update in Mongo only affects the first document and to update all documents resulting from the query one needs to use 'Multi' update flag. Sample code follows...
var collection = db.GetCollection("Foo");
var query = Query.GTE("No", 1); // need to construct in such a way that it will give all 20K //docs.
var update = Update.Set("timestamp", datetime.UtcNow);
collection.Update(query, update, UpdateFlags.Multi);
You can use updateMany() methods of mongodb to update multiple document
Simple query is like this
db.collection.updateMany(filter, update, options)
For more doc of uppdateMany read here
As per your requirement the update code will be like this:
User.updateMany({"created": false}, {"$set":{"created": true}});
here you need to use $set because you just want to change created from true to false. For ref. If you want to change entire doc then you don't need to use $set

Upserts in mongodb when using custom _id values

I need to insert a document if it doesn't exist. I know that the "upsert" option can do that, but I have some particular needs.
First I need to create the document with its _id field only, but only if it doesn't exist already. My _id field is a number generated by me (not an ObjectId). If I use the "upsert" option then I get "Mod on _id not allowed"
db.mycollection.update({ _id: id }, { _id: id }, { upsert: true });
I know that we can't use the _id in a $set.
So, my question is: If there any way to a "create if doesn't exists" atomically in mongodb?
EDIT:
As proposed by #Barrie this works (using nodejs and mongoose):
var newUser = new User({ _id: id });
newUser.save(function (err) {
if (err && err.code === 11000) {
console.log('If duplicate key the user already exists', newTwitterUser);
return;
}
console.log('New user or err', newTwitterUser);
});
But I still wonder if it is the best way to do it.
I had the same problem, but found a better solution for my needs. You can use that same query style if you simply remove the _id attribute from the update object. So if at first you get an error with this:
db.mycollection.update({ _id: id }, {$set: { _id: id, name: 'name' }}, { upsert: true });
instead use this:
db.mycollection.update({ _id: id }, {$set: { name: 'name' }}, { upsert: true });
This is better because it works for both insert and update.
UPDATE: Upsert with _id can be done without $setOnInsert, as explaind by #Barrie above.
The trick is to use $setOnInsert:{_id:1} with upsert, that way the _id is only written to if it's an insert, and never for updates.
Only, there was a bug preventing this from working until v2.6 - I just tried it on 2.4 and it's not working.
The workaround I use is having another ID field with a unique index. Eg. $setOnInsert:{myId:1}.
You can just use insert(). If the document with the _id you specify already exists, the insert() will fail, nothing will be modified - so "create if it doesn't exist" is what it's already doing by default when you use insert() with a user-created _id.
Please note that $setOnInsert don't work easily when you upsert a simple key => value object (not $set or other).
I need to use that (in PHP):
public function update($criteria , $new_object, array $options = array()){
// In 2.6, $setOnInsert with upsert == true work with _id field
if(isset($options['upsert']) && $options['upsert']){
$firstKey = array_keys($new_object)[0];
if(strpos($firstKey, '$')===0){
$new_object['$setOnInsert']['_id'] = $this->getStringId();
}
//Even, we need to check if the object exists
else if($this->findOne($criteria, ['_id'])===null){
//In this case, we need to set the _id
$new_object['_id'] = $this->getStringId();
}
}
return parent::update($criteria, $new_object, $options);
}