Native way to add createdAt in MongoDB - 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 }
)

Related

MongoDB - how to only update field if field does not exist

How can I update a mongo document with the following requirements:
Find a document by email property:
If the document exists:
If both retrieved and new document have property A, keep property A (the retrieved one).
If retrieved document property A is null or undefined or doesn't exist, update using property A of the new object.
If the document doesn't exist
Insert the new document.
The findOneAndUpdate seems not to convey the both 3 of the requirements. Thanks.
My recommendation is to go the following path:
db.getCollection('<some-collection>').update(
{ email: 'someguy#email.com' },
{
$set: {
name: "some guy",
username: someguy,
tel: '1234'
}
},
{ upsert: true }
);
Check upsert documentation:
https://docs.mongodb.com/manual/reference/method/db.collection.update/#upsert-option
Lets go through your requirements now:
3. If the document doesn't exist, insert the new document.
Yes, it will insert new document to collection if it doesnt find the document by email. Resulting document will be combination of find condition + $set + autogenerated _id, so it will look something like this:
{
_id: ObjectId(...)
email: 'someguy#email.com'
name: "some guy",
username: someguy,
tel: '1234'
}
2. If retrieved document property A is null or undefined or doesn't exist, update using property A of the new object.
All properties provided in $set will unconditionally be persisted in the database, which also covers your requirement of updating null/undefined values
3. If both retrieved and new document have property A, keep property A (the retrieved one).
If both newly provided A and database A are the same, we dont have a problem.
If As are different, dont you want to store the new A value?
If you are afraid of nulls/undefined values, you can omit them before providing object to $set.
What is the use-case for you not wanting to update database property with newly provided value?
One use-case i can see is that you want to pass createdAt in case you are creating new record, but dont want to update that value for existing records.
If thats the case, and you know those properties in advance, you can use $setOnInsert update operator. https://docs.mongodb.com/manual/reference/operator/update/#id1
So your update query can look like this:
db.getCollection('<some-collection>').update(
{ email: 'someguy#email.com' },
{
$set: {
name: "some guy",
username: someguy,
tel: '1234'
},
$setOnInsert: {
createdAt: new Date(),
updatedAt: new Date()
}
},
{ upsert: true }
);
I hope this helps!
You need not retrieve the document for updating the property A. You can use the update API of mongo to do so. Please find the psuedo code below:
db.<collection>.update({
"$or": [
{ "PropertyA": { "$exists": false } },
{ "PropertyA": null }
]
}, {$set: {"PropertyA": "NewValue"}});
The above code is for one property, but I think you can figure out how to scale it up.
Hope this helps !!

mongodb upsert with condition

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 } );

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)

Mongo add timestamp field from existing date field

I currently have a collection with documents like the following:
{ foo: 'bar', timeCreated: ISODate("2012-06-28T06:51:48.374Z") }
I would now like to add a timestampCreated key to the documents in this collection, to make querying by time easier.
I was able to add the new column with an update and $set operation, and set the timestamp value but I appears to be setting the current timestamp using this:
db.reports.update({}, {
$set : {
timestampCreated : new Timestamp(new Date('$.timeCreated'), 0)
}
}, false, true);
I however have not been able to figure out a way to add this column and set it's value to the timestamp of the existing 'timeCreated' field.
Do a find for all the documents, limiting to just the id and timeCreated fields. Then loop over that and generate the timestampCreated value, and do an update on each.
Use updateMany() which can accept aggregate pipelines (starting from MongoDB 4.2) and thus take advantage of the $toLong operator which converts a Date into the number of milliseconds since the epoch.
Also use the $type query in the update filter to limit only documents with the timeCreated field and of Date type:
db.reports.updateMany(
{ 'timeCreated': {
'$exists': true,
'$type': 9
} },
[
{ '$set': {
'timestampCreated': { '$toLong': '$timeCreated' }
} }
]
)

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);
}