Matching an array field which contains any combination of the provided array in MongoDB - mongodb

I would like to query with a specified list of array elements such that documents returned can only contain the elements I pass, but need not contain all of them.
Given documents like:
{
name: "Article 1",
tags: ["Funny", "Rad"]
}
{
name: "Article 2",
tags: ["Cool", "Rad"]
}
{
name: "Article 3",
tags: ["Rad"]
}
Here are some example arrays and their respective results.
["Rad"] should return Article 3
["Rad", "Cool"] should return Article 2 and Article 3
["Funny", "Cool"] should return nothing, since there are no articles with only one of those tags or both
I'm sure I can pull this off with $where but I'd like to avoid that for obvious reasons.

You can do this by combining multiple operators:
db.test.find({tags: {$not: {$elemMatch: {$nin: ['Rad', 'Cool']}}}})
The $elemMatch with the $nin is finding the docs where a single tags element is neither 'Rad' nor 'Cool', and then the parent $not inverts the match to return all the docs where that didn't match any elements.
However, this will also return docs where tags is either missing or has no elements. To exclude those you need to add a qualifier that ensures tags has at least one element:
db.test.find({
tags: {$not: {$elemMatch: {$nin: ['Rad', 'Cool']}}},
'tags.0': {$exists: true}
})

The accepted answer works, but isn't optimised. Since this is the top result on Google, here's a better solution.
I went all the way back to version 2.2 in the docs, which is the oldest version available, and all of them state:
If the field holds an array, then the $in operator selects the documents whose field holds an array that contains at least one element that matches a value in the specified array (e.g. <value1>, <value2>, etc.)
Source
So you can just do
db.test.find({tags: {$in: ['Rad', 'Cool']}})
which will return any entries where the tags contain either 'Rad', 'Cool', or both and use an index if available.

Related

Retrieve empty array or null in mongoDB documents

I have a collection with all the students of my school. Each document has a sports array property that lists the sports practiced by each student, but that property may appear either as sports: [] or sports: null or not appear at all.
How can I retrieve all the documents that fall in one of the aforementioned three cases?
How can I add a sport to a student which only has one sport but not expressed as array, i.e. a student that has sports: "Badminton"? Can this property become an array?
You can use the $in operator to query for docs where a field's value is any of a list of target values:
db.students.find({sports: {$in: [null, []]}})
Note that the null case also matches docs where the field isn't present.
I believe you can use $elemMatch for this:
db.students.find({ $not: { $elemMatch: { $exists: true } } })
This tells mongoDB to fail if the array exists and has values. It only returns values that are null or empty.
On supported (later) versions on MongoDB:
find({field:{$not:{$size: 0}}})

Exact match on the embedded document when field order is not known

The doc says:
To specify an equality match on the whole embedded document, use the
query document { <field>: <value> } where <value> is the
document to match. Equality matches on an embedded document require an
exact match of the specified <value>, including the field order.
Using the dot notation is not a fully satisfying solution, as it also matches docs embedding docs with more fields than than required. And I may not know which are the other possible fields, making it impossible to explicitly exclude undesirable fields.
Querying each possible combination of fields is also not desired, as I do not want to write (#fields)! queries.
How then can I find a doc that contains an embedded doc when I do not care about the order of the fields?
For example, assume a collection of documents following this schema, but the order of the fields of inner are not known or may vary, depending on how/when they were inserted:
{
_id: ObjectId()
inner: {
some_field: some_value,
some_other_field: some_other_value
}
}
If I want to get all documents containing an inner object as {some_field: some_value, some_other_field: some_other_value}, I would want to
db.collection.find({inner:{some_field: some_value, some_other_field: some_other_value}})
but I will miss the docs with an inner object as {some_other_field: some_other_value, some_field: some_value}.
Using the dot notation like
db.collection.find({"inner.some_field": some_value, "inner.some_other_field": some_other_value})
will match all the documents I am interested in, but also include noise like
{
_id: ObjectId()
inner: {
some_field: some_value,
some_other_field: some_other_value,
some_undesired_field: some_undesired_value
}
}
One way of framing this problem is that you want those two fields (in any order) and no other fields in your result.
Dot notation solves the first part of your problem and you can solve the second part by making sure inner only has 2 fields.
db.collection.find({
"inner.some_field": some_value,
"inner.some_other_field": some_other_value,
$where: function() { return Object.keys(this.inner).length === 2 }
})
I believe this is the easiest approach. The drawback to this approach is that $where can be slow for reasons detailed here: Mongodb Query based on number of fields in a record
Try passing the document that you want to find. It works for me but the order of fields is important too.
db.collection.find({"inner": {"some_field": some_value, "some_other_field": some_other_value }}).pretty();
I just read that field order is unknown. So you have to use $or to list the possibilities such as:
db.collection.find({$or:[{"inner": {"some_field": some_value, "some_other_field": some_other_value }}, {"inner": {"some_other_field": some_other_value, "some_field": some_value }}]}).pretty();

Exact match when searching in arrays of array in MongoDB

I have two questions. I found similar things but I couldn't adapt to my problem.
query = {'$and': [{'cpc.class': u'24'},
{'cpc.section': u'A'},
{'cpc.subclass': u'C'}]}
collection:
{"_id":1,
"cpc":
[{u'class': u'24',
u'section': u'A',
u'subclass': u'B'},
{u'class': u'07',
u'section': u'C',
u'subclass': u'C'},]}
{"_id":2,
"cpc":
[{u'class': u'24',
u'section': u'A',
u'subclass': u'C'},
{u'class': u'07',
u'section': u'K',
u'subclass': u'L'},]}
In this query, two documents will be fetched.
1) But I want to fetch only the second document ("_id": 2) because it matches the query exactly. That is, the second document contains a cpc element which its class equals to 24, its section equals to A, and its subclass equals to C.
2) And I want to fetch only the matching element of cpc if possible? Otherwise I have to traverse all elements of each retrieved documents; if I traverse and try to find out which element matches exactly then my first question would be meaningless.
Thanks!
1) you're looking for the $elemMatch operator which compares subdocuments as a whole and is more concise then separate subelement queries (you don't need the $and in your query by the way):
query = { 'cpc' : {
'$elemMatch': { 'class': u'24',
'section': u'A',
'subclass': u'C' } } };
2) That can be done using a projection:
db.find(query, { "cpc.$" : 1 })
The $ projection operator documentation contains pretty much this use case as an example.

Multi-key Indexing on an Entire Array

MongoDB's docs explain multi-key indexing. Consider this comment document.
{
"_id": ObjectId(...)
"title": "Grocery Quality"
"comments": [
{ author_id: ObjectId(...)
date: Date(...)
text: "Please expand the cheddar selection." },
{ author_id: ObjectId(...)
date: Date(...)
text: "Please expand the mustard selection." },
{ author_id: ObjectId(...)
date: Date(...)
text: "Please expand the olive selection." }
]
}
The docs explain that it's possible to index on the comments.text, or any comments' field. However, is it possible to index on the comments key itself?
This post demonstrates indexing on an array of strings, however, the above comments field is an array of JSON objects.
Based on Antoine Girbal's article, it appears possible to index on an array of JSON objects where each JSON object has a different key name. However, it doesn't appear possible where each JSON object in the array shares the same key names.
Example - https://gist.github.com/kman007us/6797422
Yes you can index subdocuments and they can be in a multikey index. When indexing a whole subdocuments, it will only match when searching against the whole document eg:
db.test.find({records: {hair: "brown"}})
Searches for records that match documents that are exactly {hair: "brown"} and it can use the index to find it.
If you want to find any sub documents that have hair="brown" and any other fields the dot notation is needed eg:
db.test.find({"records.hair": "brown"})
However, there is no index to use for that - so its a full table scan.
Please note: There are limitations on index size and whole documents could easily exceed that size.

MongoDB update fields of subarrays that meet criteria

I am having a problem where I need to update a specific field found in arrays contained in a bigger array that match certain criteria as of MongoDB v2.2.3.
I have the following mongodb sample document.
{
_id: ObjectId("50be30b64983e5100a000009"),
user_id: 0
occupied: {
Greece: [
{
user_id: 3,
deadline: ISODate("2013-02-08T19:19:28Z"),
fulfilled: false
},
{
user_id: 4,
deadline: ISODate("2013-02-16T19:19:28Z"),
fulfilled: false
}
],
Italy: [
{
user_id: 2,
deadline: ISODate("2013-02-15T19:19:28Z"),
fulfilled: false
}
]
}
}
Each country in the occupied array has its own set of arrays.
What I am trying to do is find the document where user_id is 0, search through the occupied.Greece array only for elements that have "deadline": {$gt: ISODate(current-date)} and change their individual "fulfilled" fields to true.
I have tried the $ and $elemMatch operators but they match only one, the first, array element in a query whereas I need it to match all eligible elements by the given criteria and make the update without running the same query multiple times or having to process the arrays client-side.
Is there no server-side solution for generic updates in a single document? I am developing using PHP though a solution to this should be universal.
I'm afraid this is not possible. From the documentation:
Remember that the positional $ operator acts as a placeholder for the first match of the update query selector. [emphasis not mine]
This is tracked in the MongoDB Jira under SERVER-1243.
There's quite a number of related feature requests in the jira, mostly under the topic 'virtual collections'.