2

I am having a lot of problems updating a nested array, I created a very simple test, and it does not seem to work, has anyone come across this:

Data:

 [ { "A": { "B": [ { "C": [ 1, 2, 3 ] }, { "C": [ 1, 2, 3 ] } ] } }, { "A": { "B": [ { "C": [ 1, 3 ] }, { "C": [ 1, 3 ] } ] } } ]

on a find:

db.arrayQuery.find({"A.B.C": { $in: [1] }})

then update:

db.arrayQuery.update({"A.B.C": { $in: [1] }},{$pull : { "A.B.C" : 1}},{multi: true})

I get a cannot use the part (B of A.B.C) to traverse the element I read some questions here suggesting I only use {$pull : { "C" : 1}}, I no longer get the error, but nothing happens.

Neo-coder
  • 7,715
  • 4
  • 33
  • 52
GMelo
  • 229
  • 1
  • 16

2 Answers2

1

Mongo $elemMatch use for this case, query as below

db.arrayQuery.update({"A.B":{"$elemMatch":{"C":{"$in":[1]}}}},{"$pull":{"A.B.$.C":{"$in":[1]}}},false,true)
Neo-coder
  • 7,715
  • 4
  • 33
  • 52
  • Thanks for the Quick answer, however this only removes the first element, not all of them, i've added multi:true but that did not do it. also, what do the true, false flags you added mean? – GMelo Apr 20 '15 at 06:16
  • first `false` means `upsert` and second `true` means `multi true` so this case all `C` which contains `1` all removes because of last `true` – Neo-coder Apr 20 '15 at 06:21
  • @GMelo check this http://docs.mongodb.org/manual/reference/operator/update/pull/ mongo pull operator removes from an existing array all instances of a value or values that match a specified query – Neo-coder Apr 20 '15 at 06:26
  • thanks for the help, did you run this code against those two records and did it work for you? Because for me it only removed a element from the first collection not the second, so my output was: {"A": {"B":[{"C":[2,3],"C":[1,2,3]}] }} {"A": {"B":[{"C":[3],"C":[1,3]}] }} it only removes the element from the first C in each B. Thanks !! – GMelo Apr 20 '15 at 07:10
  • @GMelo actually your given data was wrong now I edited it correctly, and as per my code it removes only single matching documents, I will try to solve for multiple array values. – Neo-coder Apr 20 '15 at 07:27
  • Just realised the data mistake, thanks for fixing it. – GMelo Apr 20 '15 at 10:20
  • @GMelo yes current mongo version not supported to updated multi level nested array. In this case should use some programming code or scripts to update or change document structure. – Neo-coder Apr 20 '15 at 10:40
0

MongoDB doesn't support matching into more than one level of an array since the positional operator only supports one level deep and only the first matching element. There is a JIRA ticket for this. For other similar issues, see Multiple use of the positional $ operator to update nested arrays.

You have a couple of options here; consider modifying your schema by flattening the structure so each document represents just one array level i.e :

A : {
    B: {
        C: [...]
    }
}

The other option is to use a MapReduce operation that produces a collection that has documents with properties that have values of array index positions for each corresponding arrayQuery document. The basic idea with MapReduce is that it uses JavaScript as its query language but this tends to be fairly slower than the aggregation framework and should not be used for real-time data analysis.

In your MapReduce operation, you need to define a couple of steps i.e. the mapping step (which maps each arrayQuery B array into every document in the collection, and the operation can either do nothing or emit some object with keys and projected values) and reducing step (which takes the list of emitted values and reduces it to a single element).

For the map step, you ideally would want to get for every document in the collection, the index for each B array field and another key that contains the $pull keys.

Your reduce step would be a function (which does nothing) simply defined as var reduce = function() {};

The final step in your MapReduce operation will then create a separate collection named arrayUpdates that contains the emitted operations array object along with a field with the $pull conditions. This collection can be updated periodically when you run the MapReduce operation on the original collection. Altogether, this MapReduce method would look like:

var map = function(){
    for(var i = 0; i < this.A.B.length; i++){
        emit( 
            {
                "_id": this._id, 
                "index": i 
            }, 
            {
                "index": i, 
                A: {
                   B: this.A.B[i]
                },            
                "update": {
                    "value": "A.B." + i.toString() + ".C" // this projects the $pull query with the dynamic array element positions
                }                    
            }
        );
    }
};

var reduce = function(){};

db.arrayQuery.mapReduce(
    map,
    reduce,
    {
        "out": {
            "replace": "arrayUpdates"
        }
    }
);

Querying the output collection operations from the MapReduce operation will typically give you the result:

db.arrayUpdates.findOne()

Output:

/* 1 */
{
    "_id" : {
        "_id" : ObjectId("5534da99180e849972938fe8"),
        "index" : 0
    },
    "value" : {
        "index" : 0,
        "A" : {
            "B" : {
                "C" : [ 
                    1, 
                    2, 
                    3
                ]
            }
        },
        "update" : {
            "value" : "A.B.0.C"
        }
    }
}

You can then use the cursor from the db.arrayUpdates.find() method to iterate over and update your collection accordingly:

var cur = db.arrayUpdates.find({"value.A.B.C": 1 });

// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
    var doc = cur.next();
    var update = { "$pull": {} };
    // set the update query object
    update["$pull"][doc.value.update.value] = 1;

    db.arrayQuery.update({ "A.B.C": 1 }, update );
};

This pulls out the value 1 in the C array for each document with the query

db.arrayQuery.update({ "A.B.C": 1 }, update ); 

the object update for example for the first matching document can be { $pull: {"A.B.1.C": 1} }

Hence your final result with the db.arrayQuery.find() query after running the above will be:

/* 0 */
{
    "_id" : ObjectId("5534da99180e849972938fe8"),
    "A" : {
        "B" : [ 
            {
                "C" : [ 
                    2, 
                    3
                ]
            }, 
            {
                "C" : [ 
                    2, 
                    3
                ]
            }
        ]
    }
}

/* 1 */
{
    "_id" : ObjectId("5534da99180e849972938fe9"),
    "A" : {
        "B" : [ 
            {
                "C" : [ 
                    3
                ]
            }, 
            {
                "C" : [ 
                    3
                ]
            }
        ]
    }
}
Community
  • 1
  • 1
chridam
  • 100,957
  • 23
  • 236
  • 235