61

I have below document in MongoDB(2.4.5)

{
    "_id" : 235399,
    "casts" : {
        "crew" : [ 
            {
                "_id" : 1186343,
                "withBase" : true,
                "department" : "Directing",
                "job" : "Director",
                "name" : "Connie Rasinski"
            },
            {
                "_id" : 86342,
                "withBase" : true
            }
        ]
    },
    "likes" : 0,
    "rating" : 0,
    "rating_count" : 0,
    "release_date" : "1955-11-11"
}

I want to remove withBase filed from array elements inside casts.crew ..

I tried this

db.coll.update({_id:235399},{$unset: {  "casts.crew.withBase" : 1 } },false,true)

nothing changed.

And tried this..

db.coll.update({_id:235399},{$unset: {  "casts.crew" : { $elemMatch: { "withBase": 1 } } } },false,true)

it removed entire crew array from the document.

Can someone please provide me the right query?

s7vr
  • 73,656
  • 11
  • 106
  • 127
Maria
  • 1,161
  • 3
  • 12
  • 21

3 Answers3

103

You can use the new positional identifier to update multiple elements in array in 3.6.

Something like

 db.coll.update( {_id:235399}, {$unset: {"casts.crew.$[].withBase":""}} )

$[] removes all the withBase property from the crews array. It acts as a placeholder for updating all elements in array.

Use multi true to affect multiple documents.

s7vr
  • 73,656
  • 11
  • 106
  • 127
  • 3
    Please don't downvote without explanation. Will be happy to address your concern. – s7vr Jan 10 '18 at 00:17
  • 3
    sorry I downvoted this, I can't un-downvote :) - this actually _does_ work, but only for mongodb >=3.6. And it also work with `updateMany()` – mils Jan 10 '18 at 07:00
  • 1
    @mils Added version in the answer. – s7vr Jan 10 '18 at 11:32
  • 1
    This is the correct link to the documentation of the "update all array elements" operator : https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up._S_[] – Adam Reis Jan 27 '18 at 06:56
  • This is exactly what I needed. Thank you! – Carrot May 05 '18 at 04:40
  • simple and clear answer. Good job! – dimitris tseggenes Jun 06 '20 at 20:39
  • db.alimentos.updateMany( {_id: ObjectId("5fb9f282d742c3e6884a8000")}, {$unset: {"proteins.$[].id":""}} ) in my protein array I have objects and each with id added, I needed to delete it in 42 docs, I need to write that way MongoDB shell version v4.4.2 – 0x52 Nov 22 '20 at 17:56
  • This positional operator `$[]` worked for me :+1: – Pavel Feb 24 '21 at 10:55
59

Sorry to disappoint you, but your answer

db.coll.update({
   _id:235399,
   "casts.crew.withBase": {$exists: true}
},{
   $unset: {
      "casts.crew.$.withBase" : true
   }
},false,true)

is not correct. Actually it will remove the value, BUT only from the first occurrence of the subdocument, because of the way positional operator works:

the positional $ operator acts as a placeholder for the first element that matches the query document

You also can not use $unset (as you tried before) because it can not work on arrays (and are you basically trying to remove a key from a document from the array). You also can not remove it with $pull, because pull removes all the array, not just a field of it.

Therefore as far as I know you can not do this with a simple operator. So the last resort is doing $find and then forEach with save. You can see how to do this in my answer here. In your case you need to have another loop in forEach function to iterate through array and to delete a key. I hope that you will be able to modify it. If no, I will try to help you.

P.S. If someone looks a way to do this - here is Sandra's function

db.coll.find({_id:235399}).forEach( function(doc) {
  var arr = doc.casts.crew;
  var length = arr.length;
  for (var i = 0; i < length; i++) {
    delete arr[i]["withBase"];
  }
  db.coll.save(doc);
});
antzshrek
  • 9,276
  • 5
  • 26
  • 43
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
  • 1
    Thanks so much Salvador. It worked.. Here is my function `db.coll.find({_id:235399}).forEach( function(doc) { var arr = doc.casts.cast; var length = arr.length; for (var i = 0; i < length; i++) { delete arr[i]["withBase"]; } db.coll.save(doc); } );` – Maria Nov 13 '13 at 07:04
  • Glad that it helped. I will add it to my answer with your credentials. – Salvador Dali Nov 13 '13 at 07:15
  • 1
    A more functional approach to Nancy's solution is: `db.coll.find({_id:235399}).forEach(function(doc) { doc.casts.crew.forEach(function(crew) { delete crew.withBase; }) db.coll.save(doc); });` – Tim N Aug 02 '16 at 15:31
  • 1
    Be aware this isn't transaction save. Any changes occuring after `find()` and before `save()` will be overwritten. Prefer [Veeram's solution](https://stackoverflow.com/a/47836562/4029989), if you you have MongoDB 3.6+. – smonkey Nov 23 '18 at 08:44
  • See user2683814 answer – MickaelFM Mar 04 '20 at 16:30
14

I found a way to unset this lists without having to pull up the object (meaning, just doing an update), it's pretty hackish but if you have a huge database it will make the deal:

db.coll.update({},{$unset: {"casts.crew.0.withBase" : 1, "casts.crew.1.withBase" : 1} }, {multi: 1})

In other words, you have to calculate how many objects there can be in any of your documents list and add those numbers explicitly, in this case as {casts.crew.NUMBER.withBase: 1}.

Also, to count the longest array in a mongodb object, an aggregate can be done, something like this:

db.coll.aggregate( [   { $unwind : "$casts.crew" },   { $group : { _id : "$_id", len : { $sum : 1 } } },   { $sort : { len : -1 } },   { $limit : 1 } ], {allowDiskUse: true} )

Just want to emphasize that this is not a pretty solution but is way faster than fetching and saving.

Hassek
  • 8,715
  • 6
  • 47
  • 59