I recently needed to update specific values in sub arrays or nested arrays in MongoDB to lower case. At the end of my research and experiments I found a few variants of how to do it. I'll show you all of them using a shell to be language agnostic. Concepts are the same and it will be easy to write such queries in any language.
First of all we need to create a demo collection to play with it and make things clear:
use 4devs
db.users.insert({type: 'developer', nickname: 'victor', skills: ['PHP', 'SYMFONY', 'JAVASCRIPT', 'MONGODB', 'GIT']})
My real collection isn't related to users and is more complicated, has nested documents with arrays, but for explanation this collection is quite good.
Solution with forEach
db.users.find({type: 'developer'}).forEach(function(dev) {
dev.skills = dev.skills.map(function(skill) {
return skill.toLowerCase();
});
db.users.save(dev);
})
db.users.find().pretty()
{
"_id" : ObjectId("53edd8ec32d7a10deb5ab65d"),
"type" : "developer",
"nickname" : "victor",
"skills" : [
"php",
"symfony",
"javascript",
"mongodb",
"git"
]
}
Here we're iterating through all the needed documents (type === developer) and changing skills to lower case and then saving changes back to the collection.
Pros:
+ easy to understand and quick to write
+ flexible. You can do any modifications of document using poor JavaScript
Cons:
- for each document one query will be executed
MongoDB 2.6 $map solution
In MongoDB 2.6. we have a new operator in Aggregation Pipeline Freamework, called $map. We can use $map operator in $project stage, it iterates through all the elements in an array and applies expression to them. Let's see how we can do the same update to the documents, using $map operator:
db.users.aggregate([
{
$project: {
_id: 1,
nickname: 1,
type: 1,
skills: {
$cond: {
if: {
$eq: ['$type', 'developer']
},
then: {
$map: {
input: '$skills',
as: 'skill',
in: {
$toLower: '$$skill'
}
}
},
else: '$skills'
}
}
}
},
{
$out: 'users'
}
])
We can apply $map operator in $project stage. Also we need to keep in mind that we need to work with all the documents in the collection, because in the end we will use $out (available only from 2.6 too) stage to replace the old collection with the modified one.
To achieve the needed result we need to write all the fields in $project stage even if we don't change them:
_id: 1, nickname: 1, type: 1
Then we need to apply the changes only to developers, so we need to use $cond operator. It works as a ternary operator and has three main parts: if, then, else. If type === developer (here we use $eq operator to check this), then we need to apply a $toLower operator, else - or leave it as it is.
To apply $toLower operator for each value in the skills array we use $map operator, that has three parts too:
input - use it to say through what field you want to iterate. In our example we want to iterate through skills, so we use: input: '$skills'
as - here you can set an alias for the current element of array. We used skill: as: 'skill'
in - here you can apply expression to each element of the array. To access the element here we need to use its alias and $$: '$$skill'
That's all. Using the aggregation query we updated all the needed fields in our collection.
Pros:
+ one query
Cons:
- complicated query
- isn't so flexible. you can only use MongoDB's expressions
- need to work with all documents in a collection, and apply changes after $cond check
- this approach works only for arrays (or null). If at least one document has another type for $map field, then you'll get an error
- you need to write all document fields in $project stage, even if you don't change them (nickname: 1 and so on)
- too much overhead for big collections because of full rewrite
MongoDB 2.4
if you use MongoDB 2.4, then you're out of luck and can't use $map operator, also $out operator is unavailable too. The only choice for you is forEach.
Conclusion
I solved my problem with the forEach variant written in PHP because my collection was more complex and I couldn't apply the $map variant. Here I wanted to show that almost any problem can be solved in many ways, even if an arsenal of MongoDB seems limited to you.