February 27, 2012

MongoDB - Updating Records

Introduction

I have covered the basic queries (select, insert and delete) of MongoDB in my last post, now I will describe here how to update records in MongoDB. Update recrods is one the four basic operations CRUD (create, read, update and delete) for any database we required. MondoDB also provided its update queries, with more sophisticated tasks. Update queries funtion in Mongo is not straight forward like sql databases, its a little more enhanced functional component than simple update. You will find that it facilitates the general operations of routine development tasks, and we could made our database operations with less complications in the data layer.

Background

Update queries are capable to do the following jobs :
  • It could replace the entire document (recall, you could think of it as DataRow in .Net environment)
  • Simply update the recrods like in simple sql databases, the number of fields you specify will be updated with the provided condition.
  • Provide special $inc modifier, if you need to increment or decrement a field's value.
  • $push modifier adds a value to the array (recall, you could place entire arrays in your document's fields)
  • An upsert updates the document if found or inserts it if not.

Description

In its simplest form, update takes 2 arguments: the selector (where) to use and what field to update with. If Imran has to change the category he played, we could execute:
db.KarachiTigers.update({name: 'Imran'}, {category: 'Bowler'})
If this was real code, you'd probably update your records by _id, but since I don't know what _id MongoDB generated for you, we'll stick to names. Now, if we look at the updated record:
db.KarachiTigers.find({name: 'Imran'}) 
You should discover updates first surprise. No document is found because the second parameter we supply is used to replace the original. In other words, the update found a document by name and replaced the entire document with the new document (the 2nd parameter). This is different than how SQL's update command works. In some situations, this is ideal and can be leveraged for some truly dynamic updates. However, when all you want to do is change the value of one, or a few fields, you are best to use MongoDB's $set modifier:
db.KarachiTigers.update({name: 'Imran'}, {$set: {category: 'Bowler',
dob: new Date (1979, 7, 18, 18, 44), hobbies: ['cricket'], gender: 'm'}}) 
This'll reset the lost fields. It won't overwrite the new category since we didn't specify it. Now if we execute:
db.KarachiTigers.find({name: 'Imran'}) 
We get the expected result. Therefore, the correct way to have updated the category in the first place is:
db.KarachiTigers.update({name: 'Imran'}, {$set: {category: 'Bowler'}}) 
In addition to $set, we can leverage other modifiers to do some interesting things. All of these update modifiers work on fields - so your entire document won't be wiped out. For example, the $inc modifier is used to increment a field by a certain positive or negative amount. For example, if Amir was incorrectly awarded a wicket , we could correct the mistake by executing:
db.KarachiTigers.update({name: 'Amir'}, {$inc: {wickets: -1}})
If Saeed Ajmal has to update his bolwing actions, e.g, he could bowl off-spin and doosra, we could set the array items defined in actions field of the document:
db.KarachiTigers.update({name: 'Saeed Ajmal'},
{$set: {actions: ['off-spin', 'doosra']}})

Let's say, if Saeed Ajmal suddenly developed a teesra bowling action, we could add a value to his actions field via the $push modifier:
db.KarachiTigers.update({name: 'Saeed Ajmal'}, {$push: {actions: 'teesra'}})
The Updating section of the MongoDB website has more information on the other available update modifiers.

One of updates more pleasant surprises is that it fully supports upserts. An upsert updates the document if found or inserts it if not. Upserts are handy to have in certain situations and, when you run into one, you'll know it. To enable upserting we set a third parameter to true. An everyday example is a hit counter for a website. If we wanted to keep an aggregate count in real time, we'd have to see if the record already existed for the page, and based on that decide to run an update or insert. With the third parameter omitted (or set to false), executing the following won't do anything:
db.hits.update({page: 'cricket updates'}, {$inc: {hits: 1}});
db.hits.find();
However, if we enable upserts, the results are quite different:
db.hits.update({page: 'cricket updates'}, {$inc: {hits: 1}}, true);
db.hits.find();
Since no documents exists with a field page equal to cricket updates, a new document is inserted. If we execute it a second time, the existing document is updated and hits is incremented to 2.
db.hits.update({page: 'cricket updates'}, {$inc: {hits: 1}}, true);
db.hits.find();
A non-upsert update may or may not modify an existing object. An upsert will either modify an existing object or insert a new object. The client may determine if its most recent message on a connection updated an existing object by subsequently issuing a getlasterror command ( db.runCommand( "getlasterror" ) ).
  • If the result of the getlasterror command contains an updatedExisting field, the  last message on the connection was an update request.
  • If the updatedExisting field's value is true, that update request caused an existing object to be updated;
  • If updatedExisting is false, no existing object was updated.
  • An "upserted" field will contain the new _id value if an insert is performed.
The final surprise update has to offer is that, by default, it'll update a single document. So far, for the examples we've looked at, this might seem logical. However, if you executed something like:
db.KarachiTigers.update({}, {$set: {qualified: true }});
db.KarachiTigers.find({qualified: true});
You'd likely expect to find all of your team members to be qualified. To get the behavior you desire, a fourth parameter must be set to true:
db.KarachiTigers.update({}, {$set: {qualified: true }}, false, true);
db.KarachiTigers.find({qualified: true});
We concluded our introduction to the basic CRUD operations available against a collection. We looked at update in detail and observed three interesting behaviors. First, unlike an SQL update, MongoDB's update replaces the actual document. Because of this the $set modifier is quite useful. Secondly, update supports an intuitive upsert which is particularly useful when paired with the $inc modifier. Finally, by default, update only updates the first found document. Do remember that we are looking at MongoDB from the point of view of its shell. The driver and library you use could alter these default behaviors or expose a different API.