6

I have a mongodb document with the following structure

> db.user.find().limit(1);
{ "_id" : "1", "foo" : { "bars" : [
    {
        "name" : "bar1"
    },
    {
        "name" : "bar2"
    },
], ... }, ... }

I want to add a new property to each bar. I've got my script iterating over the bars array, but I can't get the new property in there, how can I do this?

var users = db.user.find({"foo.bars":{$exists:true}});

users.forEach(function(user) {
    user.foo.bars.forEach(function(bar)
    {
       printjson(bar);
       //how can I specify the current 'bar' in this update?
       //db.experience.update({_id: user._id}, {$set: {"what goes here?" : "newbarValue"}});
    });
});
markdsievers
  • 7,151
  • 11
  • 51
  • 83
  • Are you sure that " want to add a new property to each bar" actually worked? Especially what do you want with 'user' inside {"user.foo.bars.${bar}? And what should the '${bar}' do? Can it be that you are trying to invent new syntax without having checked how things actually work? –  Jul 20 '11 at 04:19
  • "user.foo.bars.${bar}" is indicative - it's not valid syntax, you will notice its commented out. I was trying to express my intent. – markdsievers Jul 20 '11 at 04:32

3 Answers3

10

So says the preacher man:

var users = db.user.find({"foo.bars":{$exists:true}});

users.forEach(function(user) {
    var id = user._id;

    var foo_bars = user.foo.bars;
    var new_foo_bars = [];

    for(var i = 0; i < foo_bars.length; i++) {
        var foo_bar = foo_bars[i];
        foo_bar['newkey'] = 'newvalue';
        new_foo_bars.push(foo_bar);
    }

    db.user.update({"_id":id}, {$set:{"foo.bars":new_foo_bars}});
});
stuartrexking
  • 657
  • 5
  • 12
  • 2
    AFAIK, using forEach does not scale well, it would lock the entire database for as long as the update runs. – Johann Philipp Strathausen Jan 31 '12 at 16:44
  • great answer but is there a way to avoid using foreach ? i am using the above method to retrieve a variable from another collection and add it to the result set of a collection before rendering the template. In sql i would do a join by id is there something similar for mongo? – Thordin9 Aug 18 '13 at 10:03
1

I have noticed that you are scrolling through the array on the client side there in JS.

If you were to form a new "bars" array from the old one then push it in as a whole new value this would mean you only do one DB call and the code is quite elegant.

If MongoDB does not support it normally it is better to just do the work on the client side.

Sammaye
  • 43,242
  • 7
  • 104
  • 146
  • right -- you can add a single item under one of the array elements with $set by not to all of them. so do it client side. you can run the client script on the server via localhost and it should then perform pretty well. – dm. Jul 20 '11 at 14:18
0

You have to update each element of the nested document individually. Using the positional operator '$' will not work since it will only work on the first match (as documented).

  • What is not useful here? You just can't do it differently. –  Jul 20 '11 at 05:19
  • "You have to update each element of the nested document individually. " is essentially what I was asking how to do - restating it as an answer was not useful. Pointing me at the relevant documentation or providing a line of pseudo code would have been wonderful. Spent an hour trying to nut this one out before coming here - so I was dissapointed it gets down voted and suggested that Ive not already attempted to self solve with doco. – markdsievers Jul 20 '11 at 05:46
  • 1
    you can just use hard indices in array $set operations. So db.user.update({_id: user._id}, {$set: {'foo.bars.0.someValue' : "newbarValue"}}) works. All you have to do is replace 0 with a counter that you increase in your inner for loop if i understand your problem correctly. – Remon van Vliet Jul 20 '11 at 09:11
  • @Remon. Thanks, it's a good suggestion. If I hard code an index in there, as you say it does work, but I can't seem to get update(...) to work when I try to use either a variable index in that string, or extracting a variable that builds "foo.bars." + i + ".someValue" and uses that variable in the update(...) function. – markdsievers Jul 20 '11 at 09:59
  • Yes you can't do it in JS (or at least I don't know how either, not an expert) I don't think but that's only a problem in the shell or if your using node. Most other languages will do that just fine. – Remon van Vliet Jul 20 '11 at 10:06