1

I am currently using a dynamic attributes model with the next structure:

{
  SKU: "Y32944EW",
  type: "shoes",
  attr: [
      { "k": "manufacturer", 
        "v": "ShoesForAll",
      },
      { "k": "color", 
        "v": "blue",
      },
      { "k": "style", 
        "v": "comfort",
      },
      { "k": "size", 
        "v": "7B"
      }
  ]
}

What I need to do is update some of the attributes based on other attributes conditions, for example, lets suppose that I want to update the color to red and style to sport where manufacturer is "ShoesForAll", If I do it like this:

collection.update({"attr": { "$elemMatch" : { "k":"manufacturer", "v":"ShoesForAll" } },
  {$set: {
    attr: [ 
     { "k": "color", 
        "v": "red",
      },
      { "k": "style", 
        "v": "sport",
      },]
  }}
});

I am losing other attributes like "size", and if I do it like this:

collection.update({
    "attr": { "$elemMatch" : { "k":"manufacturer", "v":"ShoesForAll" }},
    "attr.k": "color",
    "attr.k": "style"
    }, {$set: {
            "data.$.v": "red",
            "data.$.v": "sport"  
    }})

Only one attribute is updated. Anyone knows how can I update some of the attributes without losing the others ones or without using one query for each attribute?

Thanks

Shonek
  • 11
  • 1
  • 3

4 Answers4

1

Using Mongo Multi Update first pull all k:[color,style] and then addToSet in attr array given values. Update query look like this as below :

db.runCommand({
  "update": "sku",//here sku is collection name
  "updates": [{
    "q": {
      "attr.k": "manufacturer",
      "attr.v": "ShoesForAll"
    },
    "u": {
      "$pull": {
    "attr": {
      "k": {
        "$in": ["color", "style"]
      }
    }
      }
    },
    "multi": true
  }, {
    "q": {
      "attr.k": "manufacturer",
      "attr.v": "ShoesForAll"
    },
    "u": {
      "$addToSet": {
    "attr": {
      "$each": [{
        "k": "color",
        "v": "red"
      }, {
        "k": "style",
        "v": "sport"
      }]
    }
      }
    }
  }]
})
Neo-coder
  • 7,715
  • 4
  • 33
  • 52
  • Whilst a valid "raw" database command, I would personally find it less "scary" to advocate usage of [`.builkWrite()`](https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/) as opposed to the "monster" of using the commands directly. – Neil Lunn May 24 '17 at 03:37
  • Also not really sure about the usage of `$pull` and `$addToSet` here, since the net result not really that much better than using `$set`. True it's a "little" better in that it should not overwrite, but it does still leave a window where both "nothing" is in the array **and** the possibility that "something else" has actually added information in between the `$pull` and the `$addToSet`. The result would be "mutiple values for keys" if indeed something else wrote say `"red": "blue"` in between the operations. So it's a little dangerous. – Neil Lunn May 24 '17 at 03:39
1

I have tried forEach to solve this problem. It is currently working fine for the test data . The code snippet is as follows:-

db.<collectionName>.find({"attr":{"k":"manufacturer","v":"ShoesForAll"}}).forEach(function(item){

item.attr.forEach(function(val){
    if(val.k == "color"){
        val.v = "red"
    }
    if(val.k == "style"){
        val.v = "sports"
    }
})
db.<collectionName>.save(item);
});

The above code first finds all the documents that satisfies the condition of "manufacturer" is "ShoesForAll" . Then in each document , we iterate through the "attr" array , update the valid fields and finally save it . No unnecessary data loss occurs.

You have also mentioned that the filtering criteria can be more than one, in that case use logical operators ($and , $or). Giving an example:-

db.<collectionName>.find({$or: [{"attr":{"k":"manufacturer","v":"ShoesForAll"}},{"attr":{"k":"manufacturer","v":"formal"}}]}).forEach(function(item){

item.attr.forEach(function(val){
    if(val.k == "color"){
        val.v = "red"
    }
    if(val.k == "style"){
        val.v = "sports"
    }
})
db.<collectionName>.save(item);
});

Replace the collectionName along with the brackets in the appropiate places.

Echo
  • 570
  • 1
  • 7
  • 21
  • In real world environments, an approach like `.find()` -> modify -> `.save()` should always be avoided. The basics are that such a paradigm required data to be retrieved from the server to the client in order to make the modification. This introduces **big** problems. 1.) The network overhead of transporting the data back and forth. 2.) The very strong possibility that said `.save()` is actually going to "overwrite" changes made by another process, and possibly in the "whole" document and not just the fields modified. To be avoided at all costs. – Neil Lunn May 24 '17 at 03:47
  • Does .find() -> modify -> .save() always involve the client side? Can it not take place at the server side such as in a Node.js environment? – Treefish Zhang May 24 '17 at 17:31
0

Per the documentation on Set Elements in Arrays, the following works if all your documents have the same order in attributes:

db.shoes.update(
    {"attr": {
        "$elemMatch" : {
            "k":"manufacturer",
            "v":"ShoesForAll" }
        }
    },
    {$set: {
        "attr.1":
             { "k": "color",
                "v": "red",
              },
        "attr.2":
              { "k": "style",
                "v": "sport",
              }
        }
    }
)
Treefish Zhang
  • 1,131
  • 1
  • 15
  • 27
  • 1
    It's never a great idea to `$set` via an index like this in any real world situation. You simply cannot rely that something is actually maintained in the index position,and has not been moved or otherwise invalidated by some other operation. Code bases grow and problems like this seep in. Also your interpretation of index values is incorrect. `n-1` in just about every competent language implementation. – Neil Lunn May 24 '17 at 03:42
  • On ' Also your interpretation of index values is incorrect. n-1 in just about every competent language implementation.': would you please divulge what indexes 'color' and 'style' should be on, respectively? – Treefish Zhang May 24 '17 at 17:44
  • It's `attr.0` and `attr.1`. Position `n` minus `1`. But the point is that you and nobody else should ever do this for the already mentioned reasons. – Neil Lunn May 25 '17 at 05:42
  • Thank you! The point taken. For the index though, color and style were the second and third of [attr], respectively. Won't that make them attr.1 and attr.2, respectively by the n-1 rule? – Treefish Zhang May 25 '17 at 13:23
0

I try to emulate an approach to conditional update by defining shoe_update.js as below:

var MongoClient = require('mongodb').MongoClient,
    test = require('assert');

MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {

  // Get a collection
  var shoes = db.collection('shoes');

  shoes.update({
        "attr": {
            "$elemMatch": {
                "k": "manufacturer",
                "v": "ShoesForAll"
            }
        },
        "attr.k": "style"
    }, {
        $set: {
            "attr.$.v": "sport"
        },
    },
    {
        multi: true
    },
    function(err, numAffected) {
        shoes.update({
                "attr": {
                    "$elemMatch": {
                        "k": "manufacturer",
                        "v": "ShoesForAll"
                    }
                },
                "attr.k": "color"
            }, {
                $set: {
                    "attr.$.v": "red"
                }
            },
            {
                multi: true
            },
            function(err, numAffected) {
              db.close();
              }
        )
    }
  )
})

And then spin up the mongod and run Node shoe_update.js.

Treefish Zhang
  • 1,131
  • 1
  • 15
  • 27