简体   繁体   中英

How to compare elements in array in the same document?

I've found ways to compare values between arrays in diff documents that are in the same MongoDB collection, but how would you compare elements, by index, in the same array, in one document? Here's the entry in question:

{ "_id" : ObjectId("1"), "arr" : [ { "int" : 100 }, { "int" : 10 } ] }

I've got a collection with a ton of these entries (simplified here of course), and I'd like to query the collection, check each entry, and if arr[0].int > arr[1].int return those documents. Even better would be having the logic for determining what percent the element at index 1 is diff from the element at index 0 . So in this case here, for example, index 1 is 10 times less than index 0

The following works for querying if an element in a mongoDoc is greater than a given value:

db.collection.find( { "arr.0.int" : { $gt: 10 }})

I've played with this - but nothing has proved useful. Since the dataset is large - performance considerations would be awesome!

Thanks!

The basic case here is to simply apply a condition with $redact and $arrayElemAt in order to examine at each index:

db.collection.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$gt": [
          { "$arrayElemAt": ["$arr.int", 0] },
          { "$arrayElemAt": ["$arr.int", 1] }
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

This is a special pipeline stage that employs $cond in a way where the "if" condition is met "then" we "$$KEEP" the document in results, otherwise "else" we "$$PRUNE" it from the results.

This uses native operators and is as "performant" as such a query dependent on calculation is ever going to get.

MongoDB 3.6 allows a little shorter syntax on this using $expr :

db.collection.aggregate([
  { "$match": { 
    "$expr": {
      "$gt": [
        { "$arrayElemAt": ["$arr.int", 0] },
        { "$arrayElemAt": ["$arr.int", 1] }
      ]
    }
  }}
])

Which can even actually be used in a .find() :

db.collection.find({
  "$expr": {
    "$gt": [
      { "$arrayElemAt": ["$arr.int", 0] },
      { "$arrayElemAt": ["$arr.int", 1] }
    ]
  }
})

And all versions since release have supported JavaScript evaluation via $where :

db.collection.find({
  "$where": "return this.arr[0].int > this.arr[1].int"
})

But since that requires the evaluation of a JavaScript expression for each document then it's not as performant as using the native operators.

Also the only way you get your "ratio" response is by using an aggregation pipeline which can actually "alter" the results returned, which a .find() query cannot do:

db.collection.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$gt": [
          { "$arrayElemAt": ["$arr.int", 0] },
          { "$arrayElemAt": ["$arr.int", 1] }
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }},
  { "$addFields": {
    "ratio": {
      "$divide": [
        { "$arrayElemAt": ["$arr.int", 1] },
        { "$arrayElemAt": ["$arr.int", 0] }
      ]         
    }
  }}
])

There's nothing great about "calculations as a condition" at any rate, since there simply is no other option other than to run through every document in the collection and see if it matches conditions.

So if this is "common logic" then you would be better storing that in your document instead. ie:

{ "arr": [{ "int": 100 },{ "int": 10 }], "firstIsGreater": true }

Then you actually "can" use an index for selection in an efficient way. So it would be up to your application logic to write this condition as you altered the content in the document, and thus allowing you not to need such a calculation.

If you cannot model and store in such a way, then calculation in either form is all you have. So it's generally better to put some thought into why you think you need such a calculated condition in the first place.

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