Using variable field names when running modifier operations on an embedded object in mongodb

When running modifier operations on an embedded object you would often run into situations where in the field name needs to be generated/referenced dynamically when the query runs.

Lets take an example, assume we have a db testing and in that we have a collection testing, in that lets insert some documents like

db.testing.save({key:1,data:{0:1,1:2,2:3}})

using the following commands

> use testing
switched to db testing
> db.testing.remove({})
> db.testing.save({key:1,data:{0:1,1:2,2:3}})
> db.testing.save({key:2,data:{0:1,1:2,2:3}})
> db.testing.save({key:3,data:{0:1,1:2,2:3}})
> db.testing.find()
{ "_id" : ObjectId("4d0259f72715000000004d70"), "key" : 1, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
{ "_id" : ObjectId("4d0259f72715000000004d71"), "key" : 2, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
{ "_id" : ObjectId("4d0259f72715000000004d72"), "key" : 3, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
>

Now if we would want to increment every value of the embedded data(testing.data), we would normally be running a command like

> db.testing.update({key:1},{$inc:{"data.0":1},$inc:{"data.1":2},$inc:{"data.2":3}},false)
> db.testing.find()
{ "_id" : ObjectId("4d0259f72715000000004d70"), "key" : 1, "data" : { "0" : 1, "1" : 2, "2" : 6 } }
{ "_id" : ObjectId("4d0259f72715000000004d71"), "key" : 2, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
{ "_id" : ObjectId("4d0259f72715000000004d72"), "key" : 3, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
>

Now if you noticed we had to build individual increment commands for each field, this may not work out

  • when there are large such fields to update, or
  • when the field names to update are known at runtime for example in a map-reduce

You cannot use a variable substituion there since the field name in the increment operation maps to a string, if you subtitute it with a variable it would be inserted with a fieldname as the variable.

> var aField = "data.0"
> db.testing.update({key:1},{$inc:{aField:1}},false)
> db.testing.find()
{ "_id" : ObjectId("4d0259f72715000000004d70"), "aField" : 1, "data" : { "0" : 1, "1" : 2, "2" : 6 }, "key" : 1 }
{ "_id" : ObjectId("4d0259f72715000000004d71"), "key" : 2, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
{ "_id" : ObjectId("4d0259f72715000000004d72"), "key" : 3, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
>

Here a new field with the name aField got inserted into the first document

So how do we handle this ? The solution is simple; since the update query takes in a json object, all we need to do is to build the whole thing separately as a json object.
First in-order to bring in the dynamic update use-case, lets use an array that contains the field name as the key and value as the value to be incremented

> var aIncr = [];
> aIncr[0] = 1;
1
> aIncr[1] = 2;
2
> aIncr[2] = 3;
3
> for(pKey in aIncr) {print(pKey);print(aIncr[pKey]);}
0
1
1
2
2
3

This can be built dynamically at runtime. Now we use this to build the update query json object

> for (pKey in aIncr) {
...     incKey = "data." + pKey
...     incData = {}
...     incData[incKey] = aIncr[pKey]
...     incQuery = []
...     incQuery['$inc'] = incData
...     db.testing.update({key:1},incQuery,false)
... }
> db.testing.find()
{ "_id" : ObjectId("4d0259f72715000000004d70"), "aField" : 1, "data" : { "0" : 2, "1" : 4, "2" : 9 }, "key" : 1 }
{ "_id" : ObjectId("4d0259f72715000000004d71"), "key" : 2, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
{ "_id" : ObjectId("4d0259f72715000000004d72"), "key" : 3, "data" : { "0" : 1, "1" : 2, "2" : 3 } }
>

Here we build the increment part as separate json object that contains an array of key-value pairs(the actual fields to increment). Note you would not be able to use a dynamic object instead of an array for the increment json object due to the fact that increment modifier takes an object with a single field and value.

There you go, you can use the same approach when building any such dynamic query.

The complete working script is given below

use testing
db.testing.remove({})
db.testing.save({key:1,data:{0:1,1:2,2:3}})
db.testing.save({key:2,data:{0:1,1:2,2:3}})
db.testing.save({key:3,data:{0:1,1:2,2:3}})
db.testing.find()

var aIncr = [];
aIncr[0] = 1;
aIncr[1] = 2;
aIncr[2] = 3;
for(pKey in aIncr) {print(pKey);print(aIncr[pKey]);}

for (pKey in aIncr) {
	incKey = "data." + pKey
	incData = {}
	incData[incKey] = aIncr[pKey]
	incQuery = []
	incQuery['$inc'] = incData
	db.testing.update({key:1},incQuery,false)
}
db.testing.find()

Who's online

There are currently 0 users and 5 guests online.

Who's new

  • Saravana Peruma...