1

The json that I'm returning looks like this:

{
  "_id" : "id",
  "_class" : "ClassName",
  "key" : [ 
    {
      "_id" : "keyId",
      "array" : [ 
        {
          "arrKey" : "value"
        }
      ]
    }
  ]
}

I'm trying to query based on the key.array.arrKey[0].value.length()? The array should always only have one value.

I'm not sure if I need to do a find or aggregate and match, but below is my attempt at doing this query.

var cursor = db.getCollection('collection').find( {
    key.array.arrKey[0].value.length: {$gt: 5} 
});
vamsiampolu
  • 6,328
  • 19
  • 82
  • 183
user9333933
  • 247
  • 1
  • 3
  • 14
  • Possible duplicate of [Find in Double Nested Array MongoDB](https://stackoverflow.com/questions/29071748/find-in-double-nested-array-mongodb) – Mani Mar 04 '19 at 22:18
  • Is there something in the provided answer that you believe does not address your question? If so then please comment on the answer to clarify what exactly needs to be addressed that has not. If it does in fact answer the question you asked then please note to [Accept your Answers](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work) to the questions you ask – Neil Lunn Mar 08 '19 at 03:16

1 Answers1

1

You can use $strLenCP within $expr and some careful handling of processing through arrays:

db.collection.find({
  "$expr": {
    "$anyElementTrue": {
      "$map": {
        "input": {
          "$reduce": {
            "input": "$key.array.arrKey",
            "initialValue": [],
            "in": { "$concatArrays": [ "$$value", "$$this" ] }
          }
        },
        "in": { "$gte": [ { "$strLenCP": "$$this" }, 5 ] }
      }
    }
  }
})

If you have a MongoDB older than 3.6 you can use $where instead:

db.collection.find({
  "$where": function() {
     return this.key.reduce((o,e) => o.concat(e.array.map(a => a.arrKey)), [])
       .some(e => e.length >= 5);
  }
})

Both follow the same principal of accessing the inner array elements by "concatenating" into a single array of strings, then testing those elements to see if they matched the length requirements.


The following is to deter incorrect responses

NOTE: Before anyone else jumps on here and says "Hey, can't you just do this:"

db.collection.find({
  "$expr": { "$gte": [ { "$strLenCP": "$key.array.arrKey" }, 5 ] }
})

Then NO, you cannot, and the reason very simply is to look at what that aggregation expression notation actually does:

db.collection.aggregate([
  { "$project": {
    "value": "$key.array.arrKey"
  }}
])

Returns:

{ "_id" : "id", "value" : [ [ "value" ] ] }

So the returned value of "$key.array.arrKey" is NOT the "string" of "value" but is instead the nested array as shown above. Hence the reduce and array concatenation to a single array of strings, and then the map operation over those array elements to test each element.

That is why both the aggregation expression AND the JavaScript expression follow the same logic pattern. Since "$key.array.arrKey" sees a completely different picture to what .find({ "key.array.arrKey": { "$exists: true } }) might do things.

Aggregation expressions and query operators work very differently.

Noting of course that looking for strings longer than 6 ( or any length longer than the actual string ):

 db.collection.find({
   "$expr": { "$gte": [ { "$strLenCP": "$key.array.arrKey" }, 6 ] }
 })

would return the supplied document in the question in error.

Note also that in this case the $strLenCP is actually comparing the "stringified" notation of the array, including the brackets [] and spacing. Which is of course incorrect.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317