Using $inc to increment a field in a sub-document in an array and a field in main document

(Blog post inspired by question I answered on StackOverflow)

Lets say you have a schema in MongoDB that looks something like this:

{
  '_id' : 'star_wars',
  'count' : 1234,
  'spellings' : [ 
    { spelling: 'Star wars', total: 10}, 
    { spelling: 'Star Wars', total : 15}, 
    { spelling: 'sTaR WaRs', total : 5} ]
}

Atomically, you’d like to update two fields at one time, the total for a particular spelling which is in a sub document in an array, and the overall count field.

The way to handle this is to take advantage of the positional array operator and the $inc operator.

Starting with just updating the count, that’s easy:

db.movies.update( 
    {_id: "star_wars"}, 
    { $inc : { 'count' : 1 }}
)

The query matches on the document with the _id of star_wars, and increments the count by 1.

The positional operator is where the mongo-magic comes into play here. If you wanted to just update a single sub-document in the array, add it to the query. First, we’ll try finding the right document:

db.movies.find( {
    _id: "star_wars", 
    'spellings.spelling' : "Star Wars" }
)

That matches the right document, but also returns all of the elements of the array.

But, wait, there’s more! When you match on an element/document of an array, the position of the match is remembered and can be used in the update phase. You do that using the positional operator. Using the document above, you’ll use this: spellings.$.total. If you knew the specific index into the array, the $ could have been replaced with the zero-based index number (like a 1 for example in this case: spellings.1.total).

Putting it all together then results in a slick and simple way of incrementing multiple fields in a document.

db.movies.update( 
    {_id: "star_wars",
     'spellings.spelling' : "Star Wars" },
    { $inc : 
        { 'spellings.$.total' : 1, 
        'count' : 1 }})

Results:

{
  '_id' : 'star_wars',
  'count' : 1235,
  'spellings' : [ 
    { spelling: 'Star wars', total: 10}, 
    { spelling: 'Star Wars', total : 16}, 
    { spelling: 'sTaR WaRs', total : 5} ]
}