0

I have a data structure like this:

{
 students: [
              { name: "john", school: 102, age: 4 },
              { name: "jess", school: 102, age: 11 },
              { name: "jeff", school: 108, age: 7 }
           ]
}
{
 students: [
              { name: "ajax", school: 100, age: 7 },
              { name: "achilles", school: 100, age: 8 },
           ]
}
{
 students: [
              { name: "bob", school: 100, age: 7 },
              { name: "manuel", school: 100, age: 8 },
           ]
}

I want to update the documents such that any students who is 7 years old is changed to school 101. Is there any way to do this in 1 operation?

Harry
  • 52,711
  • 71
  • 177
  • 261

2 Answers2

1

Currently mongo only supports updating a single subdocument in an array that match the specified c. This works really well with subdocuments that have a unique identifier (e.g. _id) that you are updating based on but does not work when there are multiple subdocuments that match the criteria (e.g. student ages).

To demonstrate this, I added a student "dupey" to the first set of students so the new records looks like this:

students: [
          { name: "john", school: 102, age: 4 },
          { name: "jess", school: 102, age: 11 },
          { name: "jeff", school: 108, age: 7 },
          { name: "dupey", school: 102, age: 7 }
       ]

Here is the mongo update statement and the data result after the update completes:

> db.students.update({"students.age":7},{$set: {"students.$.school":101}},true,true);
> db.students.find({}, { _id: 0 });
{ "students" : [    {   "name" : "john",    "school" : 102,     "age" : 4 },    {   "name" : "jess",    "school" : 102,     "age" : 11 },   {   "name" : "jeff",    "school" : 101,     "age" : 7 },    {   "name" : "dupey",   **"school" : 102**,     "age" : 7 } ] }
{ "students" : [    {   "name" : "ajax",    "school" : 101,     "age" : 7 },    {   "name" : "achilles",    "school" : 100,     "age" : 8 } ] }
{ "students" : [    {   "name" : "bob",     "school" : 101,     "age" : 7 },    {   "name" : "manuel",  "school" : 100,     "age" : 8 } ] }

Notice dupey's school has not been changed. There is a mongo feature request to support this. Until then, this has to be broken up into multiple statements. Here is some code I wrote a few weeks back when I had a similar issue.

var mongoose = require('mongoose'),
    async = require('async');

mongoose.connect('mongodb://127.0.0.1/products');

var PartsSchema = new mongoose.Schema({
        type: String
        , partNbr: Number
    });

var ProductSchema = new mongoose.Schema({
        sku: { type: String, unique: true }
        , sku_type: String
        , parts: [PartsSchema]
    });

Product = mongoose.model('Product', ProductSchema);

Product.remove( function(err) {
        if (err) { console.log("unable to remove: " + err); return; }
    });

var cigars = new Product({
        sku: 'cigar123',
        sku_type: 'smoking',
        parts: [{type: 'tobacco', partNbr: 4}, {type: 'rolling paper', partNbr: 8}, {type: 'tobacco', partNbr: 4}]
    });

var cigarillo = new Product({
        sku: 'cigarillo456',
        sku_type: 'smoking',
        parts: [{type: 'tobacco', partNbr: 4}, {type: 'crush paper', partNbr: 12}]
    });

function updateProducts(sku_type, part_type, callback) {

    function updateProduct(product_record, callback) {

        function denormParts(part_records, sku_id) {
            var returnArray = [];
            part_records.forEach( function(item) {
                    var record = { sku_id: sku_id, part_type: item.type, part_id: item._id }
                    returnArray.push(record);
                });
            return returnArray;
        function updateParts(part_record, callback) {
            if (part_record.part_type != part_type) {
                return callback(null);
            }
            console.log("updating based on: " + part_record.sku_id + " " + part_record.part_id);
            Product.update( { _id: part_record.sku_id, 'parts._id': part_record.part_id },
                            { $set: { 'parts.$.partNbr': 5 } },
                            function(err, numAffected) {
                                if (err) { console.log("err4: " + err); return callback(err); }
                                console.log("records updated: " + numAffected);
                                callback(null);
                            });
        }

        var denormedParts = denormParts(product_record.parts, product_record._id);
        console.log("calling updateParts with: " + JSON.stringify(denormedParts));
        async.map(denormedParts, updateParts, function(err) {
                       if (err) { console.log("errored out: " + err); callback(err); }
                       callback(null);
                   });
    }

    Product.find({ "parts.type": part_type, "sku_type": sku_type },
                 function(err, docs) {
                     if (err) { console.log("err3: " + err); return callback(err); }
                     console.log(docs);
                     async.map(docs, updateProduct, function(err) {
                             if (err) { console.log("err4: " + err); return callback(err); }
                             callback(null);
                         });
                 }); // end Product.find                                                                                                                                                                
}


cigars.save(function(err, product1) {
        if(err) { console.log("err1: " + err); return err; }
        console.log("saved: " + product1);
        cigarillo.save(function(err, product2) {
                if(err){ console.log("err2: " + err); return err; }
                console.log("saved: " + product2);
                updateProducts('smoking', 'tobacco', function(err) {
                        Product.find({}, function(err, docs) {
                                if (err) { console.log("err5: " + err); return err; }
                                console.log("updated data: " + docs);
                                Product.remove(function(err) {
                                        if (err) { console.log("err6: " + err); return err; }
                                        process.exit();
                                    });
                            });
                    });
            });
    });

I hope this code helps you and others who run into this issue.

frank
  • 501
  • 8
  • 21
0

you can update an array by specifying its position, for example:

 db.students.update({"students.age":7},{$set: {"students.$.school":101}},true,true)