Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoid: Query based on size of embedded document array

This is similar to this question here but I can't figure out how to convert it to Mongoid syntax:

MongoDB query based on count of embedded document

Let's say I have Customer: {_id: ..., orders: [...]}

I want to be able to find all Customers that have existing orders, i.e. orders.size > 0. I've tried queries like Customer.where(:orders.size.gt => 0) to no avail. Can it be done with an exists? operator?

like image 964
jsurf Avatar asked Aug 28 '14 00:08

jsurf


People also ask

How would you query an array of embedded documents in MongoDB?

Use the $elemMatch operator to query embedded documents. Use conditional operators to query embedded documents. Use Visual Query Builder to query embedded documents.

How do I query embedded files in MongoDB?

Accessing embedded/nested documents – In MongoDB, you can access the fields of nested/embedded documents of the collection using dot notation and when you are using dot notation, then the field and the nested field must be inside the quotation marks.

How do I query an array value in MongoDB?

To query if the array field contains at least one element with the specified value, use the filter { <field>: <value> } where <value> is the element value. To specify conditions on the elements in the array field, use query operators in the query filter document: { <array field>: { <operator1>: <value1>, ... } }

How do I find the length of an array in MongoDB?

The $size operator matches any array with the number of elements specified by the argument. For example: db. collection.


2 Answers

Just adding my solution which might be helpful for someone:

scope :with_orders, -> { where(orders: {"$exists" => true}, :orders.not => {"$size" => 0}}) }
like image 44
daino3 Avatar answered Dec 07 '22 15:12

daino3


I nicer way would be to use the native syntax of MongoDB rather than resort to rails like methods or JavaScript evaluation as pointed to in the accepted answer of the question you link to. Especially as evaluating a JavaScript condition will be much slower.

The logical extension of $exists for a an array with some length greater than zero is to use "dot notation" and test for the presence of the "zero index" or first element of the array:

Customer.collection.find({ "orders.0" => { "$exists" => true } })

That can seemingly be done with any index value where n-1 is equal to the value of the index for the "length" of the array you are testing for at minimum.

Worth noting that for a "zero length" array exclusion the $size operator is also a valid alternative, when used with $not to negate the match:

Customer.collection.find({ "orders" => { "$not" => { "$size" => 0 } } })

But this does not apply well to larger "size" tests, as you would need to specify all sizes to be excluded:

Customer.collection.find({ 
    "$and" => [ 
        { "orders" => { "$not" => { "$size" => 4 } } }, 
        { "orders" => { "$not" => { "$size" => 3 } } },
        { "orders" => { "$not" => { "$size" => 2 } } },
        { "orders" => { "$not" => { "$size" => 1 } } },
        { "orders" => { "$not" => { "$size" => 0 } } }
    ]
})

So the other syntax is clearer:

Customer.collection.find({ "orders.4" => { "$exists" => true } })

Which means 5 or more members in a concise way.

Please also note that none of these conditions alone can just an index, so if you have another filtering point that can it is best to include that condition first.

like image 157
Neil Lunn Avatar answered Dec 07 '22 16:12

Neil Lunn