1

Hi I want to return all the collections that have arrays count lesser than 2 inside features array in mongodb. I tried using $size but it is not possible.

I don't want to get the result and loop each of the features and count it. I want to return the productId 123 because it has a count of 1 in one of features array. Please take below document as an example:

{
    "productId" : 123.0,
    "features" : [ 
        {
            "a" : true
        }, 
        {
            "a" : true,
            "b" : true
        }
    ]
},
{
    "productId" : 456.0,
    "features" : [ 
        {
            "a" : true,
            "b" : true
        }, 
        {
            "a" : true,
            "b" : true
        }
    ]
}

1 Answer 1

3

What you are actually asking for is matching on the "count of the number of keys" within the array elements. You have different approaches to this depending on the available MongoDB version.

MongoDB 3.4.4 and upwards

You can use $objectToArray to coerce each element into an "array" itself, representing the "key/value" pairs of the elements:

db.collection.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$anyElementTrue": { 
          "$map": {
            "input": "$features",
            "as": "f",
            "in": {
              "$lt": [
                { "$size": { "$objectToArray": "$$f" } },
                2
              ]
            }
          }
        }
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

You basically feed the condition with $redact which determines that for the results of $map where the $objectToArray is applied to each element and then tested for the $size, where any of the tested array elements returned true via $anyElementTrue.

All Other Versions

Anywhere else, looking somewhat more brief but actually not as performance effective is using $where to apply a JavaScript expression to test the array elements. Same principle though using Object.keys() and Array.some():

db.collection.find({
  "$where": function() {
    return this.features.some(f => Object.keys(f).length < 2 )
  }
})

Same deal but since the JavaScript requires interpretation and evaluation against every document, it actually runs quite a bit slower that the aggregation expression given.

Both return the same document, which is the one which has an element with "less than two keys" in the inner object, just as asked:

/* 1 */
{
    "productId" : 123.0,
    "features" : [ 
        {
            "a" : true
        }, 
        {
            "a" : true,
            "b" : true
        }
    ]
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for the answer but I can't get it working since my version of MongodDb is 2.6.12. I tried the JavaScript but it has an error pertaining to the symbol >
@KristCont If your "shell" is really ancient then it does not understand ES6 syntax. There is nothing wrong with using a more modern shell with older "server" releases. But you can also change the $where argument to return this.features.some(function(f) { return Object.keys(f).length < 2 })
I tried what you said on your comment above this but it returns error ` Object.keys called on non-object near 'ys(f).length < 2 }` .
@KristCont Not from the data you presented in the question. The only way you get an error like that that is where your actual data structure does not resemble what was posted in the question. My answer is run on the data presented. Copied and pasted into a collection and the results copied and pasted into the answer. I suggest you do the same. If you cannot work out how to apply to "data that is different" to what you actually asked about, then you Ask a New Question. But what you asked, has been answered.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.