简体   繁体   中英

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"
  },
...
]

While you could use $where as proposed by others in order to do cross field comparisons, I'd not recommend it if possible:

No indexes can be used for this search. Mongo will scan every document and execute a JavaScript expression against each one to do the cross-field comparison. This will be an expensive and slow operation.

Coming in the next version of MongoDB (2.6), you'll be able to to use the $size operator in an aggregation which could compare two fields.

But, for the best performance, I'd recommend you store a materialized boolean that would store whether the number of elements in the users array is equal to the number stored in seats . You can easily store the number of current users using $inc (positive and negative) if you don't want to fetch the entire array in order to get the count and do the comparison against the field "seats".

If you must use $where , I'd recommend you try to do some filtering if possible that might help avoid a full table scan. Maybe date ranges, or checking whether the users array is empty, etc.

您可以使用$where查询:

db.turnys.find({ $where: "this.users.length == this.seats" })

您可以将$ where运算符与包含JS表达式的String或直接使用JavaScript函数的String一起使用:

> db.turnys.find(function() { return this.users.length == this.seats })

You Could use any one of the following ways

db.flight.find({"$where":"function() { return this.users.length == this.seats}" })

or

db.turnys.find({ $where: "this.users.length == this.seats" })

or

db.turnys.find(function() { return this.users.length == this.seats })

These all will serve your purpose but the thing is efficiency and "$where" queries should not be used unless strictly necessary: they are much slower than regular queries, so try and avoid queries with "$where".

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM