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?

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.