MongoDB update all fields of array error

2019-03-04 16:08发布

问题:

Im tring to set 0 the items.qty of a document obtains by a id query.

db.warehouses.update(
    // query 
    {
        _id:ObjectId('5322f07e139cdd7e31178b78')
    },    
    // update 
    {
        $set:{"items.$.qty":0}
    },    
    // options 
    {
        "multi" : true,  // update only one document 
        "upsert" : true  // insert a new document, if no existing document match the query 
    }
);

Return:

Cannot apply the positional operator without a corresponding query field containing an array.

This is the document that i want to set all items.qty to 0

{
  "_id": { "$oid" : "5322f07e139cdd7e31178b78" },
  "items": [
    {
      "_id": { "$oid" : "531ed4cae604d3d30df8e2ca" },
      "brand": "BJFE",
      "color": "GDRNCCD",
      "hand": 1,
      "model": 0,
      "price": 500,
      "qty": 0,
      "type": 0
    },
    {
      "brand": "BJFE",
      "color": "GDRNCCD",
      "hand": 1,
      "id": "23",
      "model": 0,
      "price": 500,
      "qty": 4,
      "type": 0
    },
    {
      "brand": "BJFE",
      "color": "GDRNCCD",
      "hand": 1,
      "id": "3344",
      "model": 0,
      "price": 500,
      "qty": 6,
      "type": 0
    }
  ],
  "name": "a"
}

回答1:

EDIT

The detail missing from the question was that the required field to update was actually in a sub-document. This changes the answer considerably:


This is a constraint of what you can possibly do with updating array elements. And this is clearly explained in the documentation. Mostly in this paragraph:

The positional $ operator acts as a placeholder for the first element that matches the query document

So here is the thing. Trying to update all of the array elements in a single statement like this will not work. In order to do this you must to the following.

db.warehouses.find({ "items.qty": { "$gt": 0 } }).forEach(function(doc) {
    doc.items.forEach(function(item) {
        item.qty = 0;
    });
    db.warehouses.update({ "_id": doc._id }, doc );
})

Which is basically the way to update every array element.

The multi setting in .update() means across multiple "documents". It cannot be applied to multiple elements of an array. So presently the best option is to replace the whole thing. Or in this case we may just as well replace the whole document since we need to do that anyway.

For real bulk data, use db.eval(). But please read the documentation first:

db.eval(function() {
    db.warehouses.find({ "items.qty": { "$gt": 0 } }).forEach(function(doc) {
        doc.items.forEach(function(item) {
            item.qty = 0;
        });
        db.warehouses.update({ "_id": doc._id }, doc );
    });
})

Updating all the elements in an array across the whole collection is not simple.


Original


Pretty much exactly what the error says. In order to use a positional operator you need to match something first. As in:

db.warehouses.update(
    // query 
    {
        _id:ObjectId('5322f07e139cdd7e31178b78'),
        "items.qty": { "$gt": 0 }
    },    
    // update 
    {
        $set:{"items.$.qty":0}
    },    
    // options 
    {
        "multi" : true,  
        "upsert" : true  
    }
);

So where the match condition fins the position of the items that are less than 0 then that index is passed to the positional operator.

P.S : When muti is true it means it updates every document. Leave it false if you only mean one. Which is the default.



回答2:

You can use the $ positional operator only when you specify an array in the first argument (i.e., the query part used to identify the document you want to update).

The positional $ operator identifies an element in an array field to update without explicitly specifying the position of the element in the array.