Is it possible to pullAll on multiple matched resu

2019-07-16 18:54发布

问题:

I have following documents:

{
   _id: 1
   items: ['1a', '1b', '1c']
},
{
   _id: 2,
   items: ['2a', '2b', '2c']
}

Is it possible to pull items from these documents but only if all the items exist?

To explain:

I need to pull '1a', '1c' and '2b' elemnets from these documents, so I if issue the query like the following:

var updateObj = ['1a', '1c', '2b'];
db.collection.update({_id : {$in updateObj}}, {$pullAll: {items: updateObj}});

Than will return success and pull the items.

However if issue the following query:

var updateObj = ['1a', '1c', '2z'];
db.collection.update({_id : {$in updateObj}}, {$pullAll: {items: updateObj}});

Then I want the update to fail, because 2z does not exist in any of the documents.

回答1:

Okay, this took me a few moments to get my head around but I'm sure I understand now. Hold on because this is a lengthy explanation, and I hope I am not confusing you with the english.

As you are saying, given the arguments to $in what you want to do is $pullAll the elements from the array field, but only when all of those elements where found. (There is a possible accepted edit to your question that may clear that up by now).

As it stands, you would be aware that your second case will of course pull the matching elements ('1a', '1c') from the matching document even though your '2z' element does not match any document in the collection. With what you want to achieve I think that last statement is the most important point.

The main problem with this condition, and hence why it does not work, is because you are matching elements that span over different documents.

Take your first case that you expect to match. The specified elements ('1a', '1c', '2b') matches both of the documents you give as a sample. This is because given the $in operator, both documents meet the condition, where the first has elements ('1a', '1c') and the second matches ('2b') as one of the elements.

Here your operation is simple (provided you use a multi update) where you have matched two documents, and the update portion is simple. So for the documents that match, we just want to pull the array elements out from the list provided. And this works just fine.

In your second case, we can see that '2z' is not going to be seen in any document in the collection. But as we are spanning documents and also with the usage of $in, we find that the first document is a match for the condition ('1a' and '1c' are present and match the $in). So what this means is that $pullAll with the arguments given is going to act on this document. And as you will be aware, $pullAll does not care if all the elements do not exist, it will just pull everything that matches the list given.

So here you can see the problem, is that we are over multiple documents and one of the documents (or more) is going to match these conditions, but you want to do something different.

So can you do this in your update form? Well, no. But we can take another approach. What we want to do is test if all the elements match within documents in the collection. So for this we need to analyse first before we make the decision to update.

Take care, this is a little contrived. But bear with me:

db.collection.aggregate([
    // Look for "just what we are expecting to match"
    {$match: {items: {$in: ['1a','1c', '2z']}} },

    // Unwind the array for matching
    {$unwind: "$items"},

    // Pool this back into "one document"
    {$group: { _id: null, items: {$addToSet: "$items"}}}

    // Now ask the question again "Do you match **all**?"
    {$match: {items: {$all: ['1a', '1c', '2z']}}}
])

So what this will do, (and better explained by looking through it in steps) is look through the documents that may match your initial set and group the elements all together as an actualt set across all documents. Then we finally ask the question, "Are all of these things found within the collection?"

The process above with '2z' in the elements will not return a result, but with '2b' it will. And so, with this test performed before you issue the update statement, you can achieve the result you want.

It's not a fail on update, but you can check for the fail before you even issue the update and meet the conditons you want.

Phew! Where's my glass of wine?



回答2:

I don't really know how you get good result for the first query because you don't have any IDs which match '1a' or '1c' or '2b'.

I think that your query should looks like this: db.collection.update({items: {$in: updateObj}}, {$pullAll: {items: updateObj}}, {multi: true});