Updating a deep record in MongoDb

2019-05-04 15:34发布

问题:

I have this record in MongoDb, and am using the native API:

{
    "_children" : {
        "addressesR" : [
            {
                "id" : ObjectId("530eea01071bd1a53065c1a6"),
                "personId" : ObjectId("530eea01071bd1a53065c1a4"),
                "street" : "ivermey",
                "city" : "perth",
                "_children" : {
                }
            },
            {
                "_children" : {
                    "configId" : {
                        "a" : {
                            "_children" : [
                                {
                                    "b" : 10
                                },
                                {
                                    "b" : 20
                                }
                            ]
                        }
                    }
                },
                "city" : "perth",
                "configId" : ObjectId("530eea01071bd1a53065c1a3"),
                "id" : ObjectId("530eea01071bd1a53065c1a5"),
                "personId" : ObjectId("530eea01071bd1a53065c1a4"),
                "street" : "bitton"
            }
        ],
    }
}

I need to update, in one query, that nested 'b' to 30. I can find the record:

db.peopleR.find( { '_children.addressesR._children.configId.a._children.b': 20 } );

But I am having a hard time finding a way to update that particular value.

I am trying:

db.peopleR.update( { '_children.addressesR._children.configId.a._children.b': 20 }, { $set: { '_children.addressesR.$._children.configId.a.$._children.b': 30 }   }  )

But I am getting:

Cannot apply the positional operator without a corresponding query field containing an array.

Now, considering that for other constraints I absolutely need to update just 'b', is there a way for me to do it? Or, is it impossible to use the $ operand twice? ( that is, I can only update the inner part of an object if it's only 1 level down?)

回答1:

The problem is that you cannot use the $ operator to show matching at nested levels in an array. And most importantly you can only specify once.

As stated from the documentation, is that the $ will only match the first element found. That not only means if you had multiple matches for your query then only the first would be picked, it also means that only the first matched array position will be used. That means something like this would also not work:

{ $set: { '_children.addressesR.1._children.configId.a._children.$.b': 30 } 

As that would erroneously match the index 1 value and update an item you were not expecting.

If you could possibly update the whole array then the following would work:

{ $set: {"_children.addressesR.$._children.configId.a._children": [ { b: 10}, { b: 30} ] } }

That is as I said, because the index is matching for the first array.

But given the structure that you have, it would seem your best option is to change that top level array to be a subdocument instead. Unless in actuality this has a reason to be an array, rather than the two mixed document types that it appears to be now.



回答2:

Your query has a problem. The second $ should go after _children. But even after that the same error appears. So it seems you are right. I tried giving the exact array position for the second part and it worked.

db.peopleR.update( { '_children.addressesR._children.configId.a._children.b': 20 }, { $set: { '_children.addressesR.$._children.configId.a._children.0.b': 30 }   }  )

Seems you need to know atleast one of the array positions.

Edit: From mongodb documents (http://docs.mongodb.org/manual/reference/operator/update/positional/)

Nested Arrays The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value.