0

I need to update a MongoDB document that looks like this:

{
    "_id" : ObjectId("A"),
    "participant" : "John Doe",
    "roles" : [{
        "roleId" : ObjectId("AA"),
        "responsibilities" : [{
            "_id" : ObjectID("AAA"),
            "name" : "resp 1"
        }, {
            "_id" : ObjectID("AAB"),
            "name" : "resp 2"
        }]
    }, {
        "roleId" : ObjectId("AB"),
        "responsibilities" : []
    }]
}

The updates come in two forms:

  • Remove all role sub-documents that have zero responsibilities... this should remove role sub-document with _id = AB
  • Add new role if it does not already exist: { "roleId" : ObjectId("AC"), "responsibilities" : [] }
2
  • does the second update, add a new role to the documents where you just removed the sub-document in the first update? or are you only applying the second update to documents that didn't have a sub-document with zero responsibilties? Commented Jul 20, 2016 at 1:26
  • @devonJS the two update actions will be executed separately and not related to one another. Sometimes, I may need to add new roles and I need to ensure the role does not already exist. That can be added as a index-based constraint, but I still need to perform the check for other reasons. At other times, I want to remove a role sub-document only if the role has zero responsibilities. Commented Jul 20, 2016 at 15:35

1 Answer 1

1

Your first update:

db.getCollection('collection').update({}, 
    {
        $pull: {
            'roles': {
                'responsibilities': {$size: 0}
            }
        }
    }, {multi: true})

Basically, removes element of any (this is where multi: true comes in) "roles" array with "responsibilities" being an empty array (or size 0)

Your second update, assuming a "new role" is a role with an empty responsibilities array:

db.getCollection('collection').update(
    {
        'roles': {
            $not: {
                $elemMatch: {
                    'roleId': ObjectId("AC")
                }
            }
        }
    },
    {
        $push: {
            'roles': {
                'roleId': ObjectId("AC"), 
                'responsibilities' : []
            }
        }
    }, {multi: true})

Finds documents that don't have a roles array where an element has an empty responsibilities array, and then pushes a new role with a new ObjectId, or you can specify one yourself.

I've tested this on a small collection of 5 documents and seems to work, let me know if you need any clarification or if it does not work.

It's worth noting that querying an array where you have to iterate through the array $elemMatch is what you're looking for. When modifying and you need to iterate through an array $pull is what you want to use.

Sign up to request clarification or add additional context in comments.

6 Comments

for the second update, I want to add a role with a specific ID if that role does not already exist in the roles array. Should I not be looking for an existing role with a matching ID as opposed to a role with an empty responsibilities array?
@WebUser sorry, I misunderstood your question a bit, please see the edits I have made above, it now looks for the role, and if it can't find it, inserts that role in
That is much closer to what I was looking for. I will test it shortly. Is there a way to direct the update towards a specific document as opposed to any or all documents? One other question - can I $elemMatch on multiple role IDs instead of just one?
1) replace the first argument object with {'_id': ObjectId('_id of the specific document)}, and then take out the {multi: false} object 2) yes you can do: $elemMatch: {'roleId': {$in: [ID1, ID2, ID3]}} if you want to see if it has ANY of the roleIds or replace $in with $all if you want to check if it has ALL roleIds. Note, you won't know which ones the document is missing, so you won't know what you need to add, this works if you just want to insert the same role in each situation. You'll have to run #2 query multiple times if you want to be precise.
I am using your helpful answers to move forward. I am trying to perform an update that includes removing sub-documents (using $pull) from the roles array that don't have any responsibilities while also adding (using $push) a couple of new sub-documents into the same roles array. Since I am trying to $pull and $push on the same field within a single update operation, I get the error - Cannot update 'roles' and 'roles' at the same time. What is a recommended way of doing this, as atomically as possible?
|

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.