8

I recently started using MongoDB and I have a question regarding updating arrays in a document. I got structure like this:

{
"_id" : ObjectId(),
"post" : "",
"comments" : [
        {
                "user" : "test",
                "avatar" : "/static/avatars/asd.jpg",
                "text" : "....."
        }
        {
                "user" : "test",
                "avatar" : "/static/avatars/asd.jpg",
                "text" : "....."
        }
        {
                "user" : "test",
                "avatar" : "/static/avatars/asd.jpg",
                "text" : "....."
        }
        ...
   ]
}

I'm trying to execute the following query:

update({"comments.user":"test"},{$set:{"comments.$.avatar": "new_avatar.jpg"}},false,true)

The problem is that it update all documents, but it update only the first array element in every document. Is there any way to update all array elements or I should try to do it manually? Thanks.

Viktor Kirilov
  • 335
  • 2
  • 3
  • 6

4 Answers4

20

You cannot modify multiple array elements in a single update operation. Thus, you'll have to repeat the update in order to migrate documents which need multiple array elements to be modified. You can do this by iterating through each document in the collection, repeatedly applying an update with $elemMatch until the document has all of its relevant comments replaced, e.g.:

db.collection.find().forEach( function(doc) {
  do {
    db.collection.update({_id: doc._id,
                          comments:{$elemMatch:{user:"test",
                                                avatar:{$ne:"new_avatar.jpg"}}}},
                         {$set:{"comments.$.avatar":"new_avatar.jpg"}});
  } while (db.getPrevError().n != 0);
})

Note that if efficiency of this operation is a requirement for your application, you should normalize your schema such that the location of the user's avatar is stored in a single document, rather than in every comment.

J Rassi
  • 841
  • 5
  • 6
  • thanks for the answer. Well since mongo don't have joins I would like to make blog system with comments, and I would like to show every user avatar next to his comments. Any suggestion how should I structure my schema? – Viktor Kirilov Feb 06 '13 at 13:24
  • Conceptually, you would need to store a user=>avatar map somewhere. You could store each user's avatar in the user document (in your users collection), or, you could store a user=>avatar map for the commenters of a given blog entry in a field of that blog entry's document. See http://docs.mongodb.org/manual/applications/database-references/ for a light treatment of normalization in MongoDB, and http://docs.mongodb.org/manual/use-cases/storing-comments/ for a "user comments" use case analysis. – J Rassi Feb 07 '13 at 06:10
  • Yeah, I figured out that solution just before I saw your answer. Anyways thank you very much @Jason. – Viktor Kirilov Feb 07 '13 at 11:44
  • TypeError: db.getPrevError is not a function – Aryeh Armon Jun 09 '16 at 09:43
6

One solution could be creating a function to be used with a forEach and evaling it (so it runs quickly). Assuming your collection is "article", you could run the following:

var runUpdate = function(){
    db.article.find({"comments.user":"test").forEach( function(article) {
        for(var i in article.comments){
            article.comments[i].avatar = 'new_avatar.jpg';
        }
        db.article.save(article);
    });
};

db.eval(runUpdate);
David Welch
  • 1,941
  • 3
  • 26
  • 34
1

If you know the indexes you want to update you can do this with no problems like this:

var update = { $set: {} };
for (var i = 0; i < indexesToUpdate.length; ++i) {
  update.$set[`comments.${indexesToUpdate[i]}. avatar`] = "new_avatar.jpg";
}
Comments.update({ "comments.user":"test" }, update, function(error) {
  // ...
});
  • be aware that must of the IDE's will not accept the syntax but you can ignore it.
TBE
  • 1,002
  • 1
  • 11
  • 32
-1

It seems like you can do this:

db.yourCollection.update({"comments.user":"test"},{$set:{"comments.0.avatar": "new_avatar.jpg", "comments.1.avatar": "new_avatar.jpg", etc...})

So if you have a small known number of array elements, this might be a little easier to do. If you want something like "comments.*.avatar" - not sure how to do that. It is probably not that good that you have so much data duplication tho..

B T
  • 57,525
  • 34
  • 189
  • 207