Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB: how to compare $size of array to another document item?

MongoDB: how to do something like this, both in mongo console and via JavaScript, Node.js:

db.turnys.find( { users:{$size:seats } } )

turnydb.turnys looks like this:

[
  {
    "gId": "5335e4a7b8cf51bcd054b423",
    "seats": 2,
    "start": "2014-03-30T14:23:29.688Z",
    "end": "2014-03-30T14:25:29.688Z",
    "rMin": 800,
    "rMax": 900,
    "users": [],
    "_id": "533828e1d1a483b7fd8a707a"
  },
  {
    "gId": "5335e4a7b8cf51bcd054b423",
    "seats": 2,
    "start": "2014-03-30T14:23:29.688Z",
    "end": "2014-03-30T14:25:29.688Z",
    "rMin": 900,
    "rMax": 1000,
    "users": [],
    "_id": "533828e1d1a483b7fd8a707b"
  },
...
]
like image 389
mylord Avatar asked Mar 20 '23 01:03

mylord


2 Answers

There are real problems with the acceptance of using the $where operator in any sort of way. Especially considering some of your "later" questions that have been raised. You need to read the manual page on this operator (as given in the link).

The short points:

  • The $where operator invokes a full collection scan in order to do it's comparisons. This is bad news in "real world" cases since there is no way to "constrain" this search to use an index in any way, and thus reduce the working set.

  • By nature, this operator invokes "arbitrary JavaScript execution". And this means two things. 1.) Every document must be expanded from it's native BSON form into the "JavaScript" object form (at a cost) for each comparison. 2.) You are invoking a "JavaScript" interpreter, that is not native code, for each and every comparison.

One of the answers gave you valid warnings on this, though it did not provide you with an actual answer. So here it is:

As stated, yes you should actually be keeping a "count" of the "array" elements within your document if this is important to you. But whilst you may have seen this as valid, the proper comparison is done as follows.

So let us assume you did use $inc as suggested to keep a total_users field in your document.

db.collection.aggregate([
    { "$project": {
        "gId": 1,
        "alloc": { "$eq": [ "$total_users", "$seats" ] }
    }},
    { "$match": { "alloc": 1 } }
])

Where essentially you can $project all the fields you want in the document, just as long as you make the comparison as shown with the "alloc" field by simply doing a "logical" comparison as shown with the $eq operator.

In future releases ( very soon as of writing ) you even get to find out the $size of an array within the "aggregation" operation. So "instead" of keeping a counter on the "array" members you can do this:

db.collection.aggregate([
    { "$project": {
        "gId": 1,
        "alloc": { "$eq": [ { "$size": "$users" }, "$seats" ] }
    }},
    { "$match": { "alloc": 1 } }
])

But naturally that will come with a "cost", so still the best option is to keep that "counter" on your document, depending on your needs.

While both approaches here do *still** result in a "collection scan" (in this context since there was no other $match condition), the end result will be many times faster than the "JavaScript" execution that was shown in other answers.

Use the "native code" methods as a best practice solution.

like image 191
Neil Lunn Avatar answered Mar 22 '23 17:03

Neil Lunn


You can use a $where query:

db.turnys.find({ $where: "this.users.length == this.seats" })
like image 33
Gergo Erdosi Avatar answered Mar 22 '23 18:03

Gergo Erdosi