December 23rd, 2017

Learning MongoDB Part III: Arrays and Nested Objects



Relational Database


Document Database

One of the really powerful things about a document database like MongoDB is the ability to store arrays of values in a document. Arrays can even store sub-documents, allowing for many levels of nested data.

An example use case of an array in a document database is a user with multiple addresses. In a relational database, this address information would be stored in a separate table. To get a users addresses, the addresses table would be joined with the users table. In MongoDB, a user and their addresses can be stored in one document, making data access and updates quick and easy (with no expensive JOIN operations).

Now let's look at some array operations in the MongoDB shell. I created a document called user which holds running log information. This info consists of an array of exercise logs, an array of planned exercises, and the users personal records.

Accessing data from nested objects and arrays is easy. For example, each user has a plan array that contains exercise plans. Each item in the plan array contains two properties - a date and a distance in miles. If I'm only interested in the miles and not the date, I can use the following dot notation - plan.miles.

db.user.find({}, {'plan.miles': 1}).pretty()

If I only want the first item in the plan array, I can use the $slice operator. $slice determines the number of items to return from an array.

db.user.find({}, {_id: 1, plan: {$slice: 1}}).pretty()

$slice is given a value of 1 which returns the first element in the array. If $slice is given a negative value it returns items from the end of the array.

Indexes are easily added to nested properties using dot notation:

db.user.createIndex({'': 1})

There are numerous operators for updating arrays, including $push, $pop, and $addToSet. $pop removes an item from an array, while both $push and $addToSet add an item to an array. With $addToSet, a new item is only added if it does not already exist in the array. This is a better option if duplicates should be avoided.

// Delete the first element of the plan array db.user.update({}, {$pop: {'plan': -1}}) // Add an element to the array if it does not already exist db.user.update({}, {$addToSet: {'plan': {date: new Date("2017-12-27"), miles: 2}}})

One important operator for updating in MongoDB is findAndModify(). This command both updates and returns the newly altered document atomically1. All updates in MongoDB are performed atomically (an update is never half completed and no database connection ever sees an update in progress). However, when calling the update() and find() operations separately, there is a chance that another update is performed in between the execution of both commands. findAndModify() eliminates this problem so you know any changes reflected in the document are from the update just performed. The following query adds another sub-document to the plan array and also returns the newly updated document.

db.user.findAndModify({ query: {}, update: { $addToSet: {'plan': {date: new Date("2017-12-28"), miles: 4.1}} }, 'new': true })

These code samples begin to expose the power of arrays and sub-documents in MongoDB. A multi-table RDBMS structure is easily compressed into a single document with MongoDB. All the code samples from this post are on GitHub.

[1] Kyle Banker, Peter Bakkum, Shaun Verch, Douglas Garrett & Tom Hawkins, MongoDB In Action, 2nd ed (Shelter Island, NY: Manning, 2016), 171