How to remove an element from a doubly-nested array in a MongoDB document.

Mongodb

Mongodb Problem Overview


I have a document structure something along the lines of the following:

{
"_id" : "777",
"someKey" : "someValue",
"someArray" : [
	{
		"name" : "name1",
		"someNestedArray" : [
			{
				"name" : "value"
			},
			{
				"name" : "delete me"
			}
		]
	}
  ]
}

I want to delete the nested array element with the value "delete me".

I know I can find documents which match this description using nested $elemMatch expressions. What is the query syntax for removing the element in question?

Mongodb Solutions


Solution 1 - Mongodb

To delete the item in question you're actually going to use an update. More specifically you're going to do an update with the $pull command which will remove the item from the array.

db.temp.update(
  { _id : "777" },
  {$pull : {"someArray.0.someNestedArray" : {"name":"delete me"}}}
)

There's a little bit of "magic" happening here. Using .0 indicates that we know that we are modifying the 0th item of someArray. Using {"name":"delete me"} indicates that we know the exact data that we plan to remove.

This process works just fine if you load the data into a client and then perform the update. This process works less well if you want to do "generic" queries that perform these operations.

I think it's easiest to simply recognize that updating arrays of sub-documents generally requires that you have the original in memory at some point.


In response to the first comment below, you can probably help your situation by changing the data structure a little

"someObjects" : {
  "name1":  {
        "someNestedArray" : [
            {
                "name" : "value"
            },
            {
                "name" : "delete me"
            }
        ]
    }
}

Now you can do {$pull : { "someObjects.name1.someNestedArray" : ...

Here's the problem with your structure. MongoDB does not have very good support for manipulating "sub-arrays". Your structure has an array of objects and those objects contain arrays of more objects.

If you have the following structure, you are going to have a difficult time using things like $pull:

array [
  { subarray : array [] },
  { subarray : array [] },
]

If your structure looks like that and you want to update subarray you have two options:

  1. Change your structure so that you can leverage $pull.
  2. Don't use $pull. Load the entire object into a client and use findAndModify.

Solution 2 - Mongodb

MongoDB 3.6 added $[] operator that facilitates updates to arrays that contain embedded documents. So the problem can be solved by:

db.test.update(
  { _id : "777" },
  {$pull : {"someArray.$[].someNestedArray" : {"name":"delete me"}}}
)

Solution 3 - Mongodb

As @Melkor has commented (should probably be an answer as itself),

If you do not know the index use:

    {_id: TheMainID, "theArray._id": TheArrayID}, {$pull: 
    {"theArray.$.theNestedArray": {_id: theNestedArrayID}}}

Solution 4 - Mongodb

From MongoDB 3.6 on you can use arrayFilters to do this:

db.test.update(
  { _id: "777" }, 
  { $pull: { "someArray.$[elem].someNestedArray": { name: "delete me" } } },
  { arrayFilters: [{ "elem.name": "name1"}] }
)

see also https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/index.html#update-all-documents-that-match-arrayfilters-in-an-array

Solution 5 - Mongodb

Other example and usage could be like this:

{

	"company": {
		"location": {
			"postalCode": "12345",
			"Address": "Address1",
			"city": "Frankfurt",
			"state": "Hessen",
			"country": "Germany"
		},
		"establishmentDate": "2019-04-29T14:12:37.206Z",
		"companyId": "1",
		"ceo": "XYZ"
	},
	"items": [{
			"name": "itemA",
			"unit": "kg",
			"price": "10"
		},
		{
			"name": "itemB",
			"unit": "ltr",
			"price": "20"
		}

	]
}
  1. DELETE : Mongodb Query to delete ItemB:
db.getCollection('test').update(   
    {"company.companyId":"1","company.location.city":"Frankfurt"},
    {$pull : {"items" : {"name":"itemB"}}}
)
  1. FIND: Find query for itemB:
db.getCollection('test').find(
    {"company.companyId":"1","company.location.city":"Frankfurt","items.name":"itemB"},
    { "items.$": 1 }
)

3.UPDATE : update query for itemB:

db.getCollection('test').update
(
 {"company.companyId":"1","company.location.city":"Frankfurt","items.name":"itemB"},
 { $set: { "items.$[].price" : 90 }}, 
   { multi: true });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrshepherdView Question on Stackoverflow
Solution 1 - MongodbGates VPView Answer on Stackoverflow
Solution 2 - MongodbCuong Le NgocView Answer on Stackoverflow
Solution 3 - MongodbAryeh ArmonView Answer on Stackoverflow
Solution 4 - MongodbSepp RenferView Answer on Stackoverflow
Solution 5 - MongodbrafiquenazirView Answer on Stackoverflow