Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Insert field with array size in mongo

I have a documents in mongodb, containing some array. Now I need to have a field containing a quantity of items of this array. So I need to update documents adding this field. Simply I thought this will work:

db.myDocument.update({
     "itemsTotal": {
         $exists: false
     },
     "items": {
         $exists: true
     }
 }, {
     $set: {
         itemsTotal: {
             $size: "$items"
         }
     }
 }, {
 multi: true
 })

But it completes with "not okForStorage". Also I tried to make an aggregation, but it throws exception:

"errmsg" : "exception: invalid operator '$size'",
"code" : 15999,
"ok" : 0

What is a best solution and what I do wrong? I'm starting to think about writing java tool for calculation totals and updating documents with it.

like image 536
user1376983 Avatar asked Oct 27 '15 17:10

user1376983


People also ask

How to check the size of an array in MongoDB?

When working on projects where you need to verify the array size or find an element whose size is more or less than a specific length, MongoDB operators such as $size, $where, $exists may be used. The approaches discussed below can assist you in resolving your array length or array size issue. Suppose we have the following data.

How to index comments array in MongoDB?

MongoDB also allows indexing the array elements - in this case, fields of the comment objects of the comments array. For example, if you are querying on the comments by "comments.user" and need fast access, you can create an index for that field. Indexes on array fields are called as Multikey Indexes.

What is $addfields in MongoDB?

The $addFields stage is equivalent to a $project stage that explicitly specifies all existing fields in the input documents and adds the new fields. Starting in version 4.2, MongoDB adds a new aggregation pipeline stage $set that is an alias for $addFields. { $addFields: { < newField >: < expression >, ... } }

How do you use the $size operator in Python?

The $size operator is used to get a document that contains an array field of a specific size. It only works with arrays and accepts numeric values as parameters. It starts by comparing an array field to the size given by the user, and then it continues.


Video Answer


3 Answers

You can use the .aggregate() method to $project your documents and return the $size of the items array. After that you will need to loop through your aggregation result using the .forEach loop and $set the itemTotal field for your document using "Bulk" operation for maximum efficiency.

var bulkOp = db.myDocument.initializeUnorderedBulkOp(); 
var count = 0;

db.myDocument.aggregate([
    { "$match": { 
        "itemsTotal": { "$exists": false } ,
        "items": { "$exists": true }
    }}, 
    { "$project": { "itemsTotal": { "$size": "$items" } } }
]).forEach(function(doc) { 
        bulkOp.find({ "_id": doc._id }).updateOne({ 
            "$set": { "itemsTotal": doc.itemsTotal }
        });
        count++;
        if (count % 200 === 0) {
            // Execute per 200 operations and re-init
            bulkOp.execute();
            bulkOp = db.myDocument.initializeUnorderedBulkOp();
        }
})

// Clean up queues
if (count > 0) { 
    bulkOp.execute();
}
like image 189
styvane Avatar answered Oct 13 '22 00:10

styvane


You could initialise a Bulk() operations builder to update the document in a loop as follows:

var bulk = db.collection.initializeOrderedBulkOp(),   
    count = 0;

db.collection.find("itemsTotal": { "$exists": false },
     "items": {
         $exists: true
     }
).forEach(function(doc) { 
    var items_size = doc.items.length;
    bulk.find({ "_id": doc._id }).updateOne({ 
        "$set": { "itemsTotal": items_size }
    });
    count++;
    if (count % 100 == 0) {
        bulk.execute();
        bulk = db.collection.initializeUnorderedBulkOp();
    }
});

if (count % 100 != 0) { bulk.execute(); }
like image 24
chridam Avatar answered Oct 13 '22 00:10

chridam


This is much easier starting with MongoDB v3.4, which introduced the $addFields aggregation pipeline operator. We'll also use the $out operator to output the result of the aggregation to the same collection (replacing the existing collection is atomic).

db.myDocuments.aggregate( [
  {
    $addFields: {
      itemsTotal: { $size: "$items" } ,
    },
  },
  {
    $out: "myDocuments"
  }
] )

WARNING: this solution requires that all documents to have the items field. If some documents don't have it, aggregate will fail with

"The argument to $size must be an array, but was of type: missing"

You might think you could add a $match to the aggregation to filter only documents containing items, but that means all documents not containing items will not be output back to the myDocuments collection, so you'll lose those permanently.

like image 1
Dan Dascalescu Avatar answered Oct 12 '22 23:10

Dan Dascalescu