mongodb – How do I update multiple elements in an array?

Question:

It is necessary to update several data with one request. Here's an example request:

db.coll.update(
  { article: 100500 },
  {
    $set: { a: 555 },
    $pull: { arr: { t: { $lt: 20 } } }
  }
);

And here's an example of the document itself:

{
  article: 100500,
  a: 400,
  arr: [
    { t: 30, b: 12, n: 90 },
    { t: 10, b: 16, n: 60 }
  ]
}

My request:

  1. Updates the value of a to 555 .
  2. Removes all elements of the arr array where t < 20 .

The crux of the question: It is necessary to update the values ​​of b in the arr . That is, wherever n == 90 , the value of b must be changed to 777 . How can this query be supplemented to "kill 3 birds with one stone"?

As a result, you should get the following document:

{
  article: 100500,
  a: 555, /* Тут было: 400 */
  arr: [
    { t: 30, b: 777, n: 90 },
    /* Тут был элемент массива */
  ]
}

Answer:

I assume you have the following documents in your collection.

{
    "_id" : ObjectId("56b71025973d202a52a5e650"),
    "article" : 100500,
    "a" : 400,
    "arr" : [
        { "t" : 30, "b" : 12, "n" : 90 },
        { "t" : 20, "b" : 16, "n" : 60 }
    ]
},
{
    "_id" : ObjectId("56b7102b973d202a52a5e651"),
    "article" : 100500,
    "a" : 400,
    "arr" : [
        { "t" : 0, "b" : 12, "n" : 90 },
        { "t" : 10, "b" : 16, "n" : 60 }
    ]
},
{
    "_id" : ObjectId("56b710d4973d202a52a5e652"),
    "article" : 100500,
    "a" : 400,
    "arr" : [ 
        { "t" : 30, "b" : 12, "n" : 90 }, 
        { "t" : 27, "b" : 16, "n" : 32 }
    ]
}

First of all, you should be aware that it is not possible to update more than one element in an array using the update() method even with the multi: true updateMany() or using the updateMany() method; and all the logic behind what you are trying to do; making things more difficult.

The best way to do this is using Bulk Operations .

Solution for MongoDB version 3.2 or newer:

MongoDB 3.2 deprecated Bulk() and related methods. You need to use the .bulkWrite() method

Here we have two options:

  1. You need to use aggregation to reduce the number of documents that need to be updated. here only one step is needed in the chain: $project where we use the $filter operator.

    let requests = [];

     db.collection.aggregate([ { "$project": { "deleteElements": { "$filter": { "input": "$arr", "as": "del", "cond": { "$lt": [ "$$del.t", 12 ] } } }, "updateElements": { "$filter": { "input": "$arr", "as": "upd", "cond": { "$and": [ { "$gte": [ "$$upd.t", 12 ] }, { "$eq": [ "$$upd.n", 90 ] } ] } } } }} ]).forEach(function(document) { document.deleteElements.forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": element.t }, "update": { "$pull": { "arr": element } } } } ); }); document.updateElements.forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": element.n }, "update": { "$set": { "arr.$.b": 777 } } } } ); }); requests.push( { "updateOne": { "filter": { "_id": document._id }, "update": { "$set": { "a": 555 } } } } ); }) db.collection.bulkWrite(requests)
  2. Using the .find() method

     db.collection.find({"article": 100500}).snapshot().forEach(function(document) { document.arr.filter(function(arr) { return arr.t < 12; }).forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.t": { "$lt": 12 }}, "update": { "$pull": { "arr": element } } } } ); }); document.arr.filter(function(arr) { return arr.n === 90; }).forEach(function(element) { requests.push( { "updateOne": { "filter": { "_id": document._id, "arr.n": 90 }, "update": { "$set": { "arr.$.b": 777 } } } } ); }); requests.push( { "updateOne": { "filter": { "_id": document._id }, "update": { "$set": { "a": 555 } } } } ); }) db.collection.bulkWrite(requests);

Solution for MongoDB 2.6 or newer:

var bulk = db.collection.initializeOrderedBulkOp();
var count = 0;
db.collection.find({"article": 100500}).snapshot().forEach(function(document) {
    document.arr.filter(function(arr) { 
        return arr.t < 12; 
    }).forEach(function(element) { 
        bulk.find({ "_id": document._id, "arr.t": { "$lt": 12 } } ).updateOne({ 
            "$pull": { "arr": element } 
        });     
        count++; 
    }); 
    document.arr.filter(function(arr) { 
        return arr.n === 90; 
    }).forEach(function(element) { 
        bulk.find({ "_id": document._id, "arr.n": 90 }).updateOne({
            "$set": { "arr.$.b": 777 } 
        });    
        count++; 
    }); 
    bulk.find( { "_id": document._id } ).updateOne( { "$set": { "a": 555 } } ); 
    count++; 
    if (count % 1000 === 0) {     
        // Выпольнить после 1000 операции
        bulk.execute();     
        bulk = db.collection.initializeOrderedBulkOp(); 
    } 
})

// Очистить очереди
if (count > 0) { bulk.execute(); }

Result

{
    "_id" : ObjectId("56b71025973d202a52a5e650"),
    "article" : 100500,
    "a" : 555,
    "arr" : [
        { "t" : 30, "b" : 777, "n" : 90 },
        { "t" : 20, "b" : 16, "n" : 60 }
    ]
}
{
    "_id" : ObjectId("56b7102b973d202a52a5e651"),
    "article" : 100500,
    "a" : 555,
    "arr" : [ ]
}
{
    "_id" : ObjectId("56b710d4973d202a52a5e652"),
    "article" : 100500,
    "a" : 555,
    "arr" : [
        { "t" : 30, "b" : 777, "n" : 90 },
        { "t" : 27, "b" : 16, "n" : 32 }
    ]
}
Scroll to Top