Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

mongodb - Remove Object from Nested Array by Multiple Criteria

I have this schema document in mongoDB:

{
    "_id": UUID("cf397865-c000-4f51-8959-1aae84769706"),
    "CreationDateTime": ISODate("2016-05-06T05:09:14.589Z"),
    "WKT": "",
    "Distributions": [{
            "_id": UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
            "DeliveryType": 1,
            "DistributionData": [{
                    "Key": "Topic",
                    "Value": "Topics",
                    "Children": null
                }, {
                    "Key": null,
                    "Value": null,
                    "Children": null
                }, {
                    "Key": "Message",
                    "Value": "test",
                    "Children": null
                }
            ],
            "Schedules": [
                ISODate("2016-05-06T05:09:56.988Z")
            ]
        }
    ],
}

I want to cleaning my database.So in order to i have decided deleted null objects in DistributionData.How can i delete object that all three attributes have a null value :

 {
  "Key": null,
  "Value": null,
  "Children": null
 }.

I wrote this query :

db.Events.update(
    {},
    {$pull:
        {
            results: {
                $elemMatch: {
                    "Distributions[0].DistributionData" : {$in:[null]}
                }
            }
        }
     },
     { multi: true }
)

When i execute this query nothing happends! I know $elemMatch is mistake.. Now how can i remove json object that all feild is null in DistributionData?? I read this and this but make me Confused...

Edit

I wrote this query:

db.Events.update(
    {},
    {$pull:
        {
            Distributions : {
                DistributionData:{
                $elemMatch: {
                    "Key" : null
                }
              }
            }
        }
     },
     { multi: true }
)

This query will remove completely object inside Distributions that DistributionData array had an object with null key:

Result:

{
"_id": UUID("cf397865-c000-4f51-8959-1aae84769706"),
"CreationDateTime": ISODate("2016-05-06T05:09:14.589Z"),
"WKT" : "",
"Distributions" : [],...
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can $pull the "first match" from the "outer array" with removing "all the inner elements" simply by doing:

db.Events.updateMany(
  {
    "Distributions.DistributionData": {
      "$elemMatch": {
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "$pull": {
      "Distributions.$.DistributionData": { 
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  }
)

That is fine if you only ever have one entry in the "Distributions" array or at least only one of those entries has child array entries which would match the condition. This is how the positional $ operator works with all versions of MongoDB.

If the data would have "multiple" matches in the "outer" "Distributions" array then if you have MongoDB 3.6 you can apply the positional filtered $[<identifier>] operator to modify all matched entries:

db.Events.updateMany(
  {
    "Distributions.DistributionData": {
      "$elemMatch": {
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "$pull": {
      "Distributions.$[element].DistributionData": { 
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "arrayFilters": [
      { "element.DistributionData": {
        "$elemMatch": {
          "Key": null,
          "Value": null,
          "Children": null
        }
      }}
    ]
  }
)

In that case the arrayFilters option defines a condition by which we match entries in the "outer" array so that this can in fact apply to everything which is matched.

Or indeed since $pull essentially has those conditions itself, then you can alternately just use the positional all $[] operator in this case:

db.Event.updateMany(
  {
    "Distributions.DistributionData": {
      "$elemMatch": {
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  },
  {
    "$pull": {
      "Distributions.$[].DistributionData": { 
        "Key": null,
        "Value": null,
        "Children": null
      }
    }
  }
)

Either case changes the document in the question by removing the inner item with all null keys:

{
        "_id" : UUID("cf397865-c000-4f51-8959-1aae84769706"),
        "CreationDateTime" : ISODate("2016-05-06T05:09:14.589Z"),
        "WKT" : "",
        "Distributions" : [
                {
                        "_id" : UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
                        "DeliveryType" : 1,
                        "DistributionData" : [
                                {
                                        "Key" : "Topic",
                                        "Value" : "Topics",
                                        "Children" : null
                                },
                                {
                                        "Key" : "Message",
                                        "Value" : "test",
                                        "Children" : null
                                }
                        ],
                        "Schedules" : [
                                ISODate("2016-05-06T05:09:56.988Z")
                        ]
                }
        ]
}

The "query" conditions all use $elemMatch for document selection. This is actually required for the positional $ operator in order to obtain the "position index" used for the "first match". Whilst this is not actually a "requirement" for either the positional filtered $[<identifier>] or the positional all $[] operator, it is still useful so you don't even consider documents for update which will not match the later update conditions of either the $pull or the arrayFilters options.

As for the $pull itself, the conditions here actually apply to "each" array element, so there is no need for the $elemMatch in that operation since we are already looking at the "element" level.

The third example shows that the positional all $[] operator can simply use those $pull conditions in consideration of each "inner" array element and will just apply to ALL "outer" array elements. So the actual point of the positional filtered $[<identifier>] expression is to "only" process those "outer" array elements which actually match the "inner" condition. Hence why we use $elemMatch in the consideration for matching each "inner" array element.


If you do not actually have MongoDB 3.6 at least then you are using the first form and likely repeating that until the updates finally return no more modified documents indicating that there are no more elements left that match the condition.

There is a much more detailed write up on the "alternatives" as approaches at How to Update Multiple Array Elements in mongodb, but as long as your data either suits the initial case or you actually do have MongoDB 3.6 available, then this is the correct approach here.


If you want to see the full effect of the new syntax for MongoDB 3.6. this is the alteration to the document in the question I used to verify the update statements here:

{
    "_id" : UUID("cf397865-c000-4f51-8959-1aae84769706"),
    "CreationDateTime" : ISODate("2016-05-06T05:09:14.589Z"),
    "WKT" : "",
    "Distributions" : [
            {
                    "_id" : UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
                    "DeliveryType" : 1,
                    "DistributionData" : [
                            {
                                    "Key" : "Topic",
                                    "Value" : "Topics",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            },
                            {
                                    "Key" : "Message",
                                    "Value" : "test",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            }
                    ],
                    "Schedules" : [
                            ISODate("2016-05-06T05:09:56.988Z")
                    ]
            },
            {
                    "_id" : UUID("bb95bedb-4baa-4ada-90b1-0d763e70ebfe"),
                    "DeliveryType" : 1,
                    "DistributionData" : [
                            {
                                    "Key" : "Topic",
                                    "Value" : "Topics",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            },
                            {
                                    "Key" : "Message",
                                    "Value" : "test",
                                    "Children" : null
                            },
                            {
                                    "Key" : null,
                                    "Value" : null,
                                    "Children" : null
                            }
                    ],
                    "Schedules" : [
                            ISODate("2016-05-06T05:09:56.988Z")
                    ]
            }
    ]
}

Which basically duplicates some entries both "outer" and "inner" to show how the statement removes all the null values.

NOTE arrayFilters are specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions and even those prior to the release of MongoDB 3.6.

However this is not true of the mongo shell, s


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...