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.

2 Answers 2

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)
Sign up to request clarification or add additional context in comments.

7 Comments

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?
first false means upsert and second true means multi true so this case all C which contains 1 all removes because of last true
@GMelo check this 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
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 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.
|
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
                ]
            }
        ]
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.