MongoDB not using wildcard nested array index - mongodb

I have the following collection:
{
_id: 12345,
quizzes: [
{
_id: 111111,
questions: []
}
]
},
{
_id: 78910,
quizzes: [
{
_id: 22222
}
]
}
I want to select the documents of a certain quiz from the quizzes that do not have the questions array and want to make sure that it uses the appropriate questions index. So I use the following query:
Answer.find({ 'quizzes.0.questions': { $exists: false } }).explain('queryPlanner');
Which returns:
{
queryPlanner: {
plannerVersion: 1,
namespace: 'iquiz.answers',
indexFilterSet: false,
parsedQuery: { 'quizzes.0.questions': [Object] },
winningPlan: { stage: 'COLLSCAN', filter: [Object], direction: 'forward' },
rejectedPlans: []
}
}
The query is not using any index as seen from the output. I have tried the following indexes and none get used:
{ quizzes.$**: 1 }
{ quizzes.questions: 1 }
{ quizzes.[$**].questions: 1 }
{ quizzes: 1 }
The only 1 that actually gets used:
{ quizzes.0.questions: 1 }
However this is not really practical as I may target any quiz from the quizzes array not just the first one. Is there a certain syntax for the index in my case or this is a current limitation of mongodb? Thanks!

Indexes generally do not help to answer queries in the form of "X does not exist". See also mongodb indexes covering missing values.
To verify whether the index is used, look up some data using a positive condition.

Related

mongodb - how to insert a new key/value on each array's element if not present (with mongo query)

I would like to update each elements (object) in an array of a company.
Here my actual data :
{
_id: ObjectId("60d31024860ce0400b586111")
contracts:
[
{
name: 1.pdf
url: "https://someurl"
createdAt: 2021-06-23T10:42:44.594+00:00
}
{
name: 2.pdf
url: "https://someurl"
}
{
name: 3.pdf
url: "https://someurl"
}
]
}
I would like to add a defined date on each object (in contracts) that has no "updatedAt" key.
Here what I tried :
db.companies.update({ _id: ObjectId("60d31024860ce0400b586111"),"contracts.createdAt": { $exists: false } },{ $set: { "contracts.$.createdAt": "test" } })
but I got this error :
"The positional operator did not find the match needed from the query."
I have also tried this and it works, but I don't wanna query by file name. I just wanna add "createdAt" on each elements found that has no "createdAt"
db.companies.update({ "contracts.name": "2.pdf" },{ $set: { "contracts.$.createdAt": "atest" } })
I think you need to use the filtered position operator:
$ - updates the first matched array element
$[] - updates all the matched elements with a specific condition
The specific condition is mentioned in the arrayFilters key.
db.students.update(
{ },
{ $set: { "contracts.$[element].createdAt" : "atest"} },
{ multi: true,
arrayFilters: [ { "element.createdAt": { $exists: false } } ]
}
)
multi - true is to apply the operation on all the matching documents.
Also notice, how the first query parameter is empty, which means the query runs for all documents. I used it based on the second query you wrote but you can also add in an ObjectID query there.

Mongo filter documents by array of objects

I have to filter candidate documents by an array of objects.
In the documents I have the following fields:
skills = [
{ _id: 'blablabla', skill: 'Angular', level: 3 },
{ _id: 'blablabla', skill: 'React', level: 2 },
{ _id: 'blablabla', skill: 'Vue', level: 4 },
];
When I make the request I get other array of skills, for example:
skills = [
{ skill: 'React', level: 2 },
];
So I need to build a query to get the documents that contains this skill and a greater or equal level.
I try doing the following:
const conditions = {
$elemMatch: {
skill: { $in: skills.map(item => item.skill) },
level: { $gte: { $in: skills.map(item => item.level) } }
}
};
Candidate.find(conditions)...
The first one seems like works but the second one doesn't work.
Any idea?
Thank you in advance!
There are so many problems with this query...
First of all item.tech - it had to be item.skill.
Next, $gte ... $in makes very little sense. $gte means >=, greater or equal than something. If you compare numbers, the "something" must be a number. Like 3 >= 5 resolves to false, and 3 >= 1 resolves to true. 3 >= [1,2,3,4,5] makes no sense since it resolves to true to the first 3 elements, and to false to the last 2.
Finally, $elemMatch doesn't work this way. It tests each element of the array for all conditions to match. What you was trying to write was like : find a document where skills array has a subdocument with skill matching at least one of [array of skills] and level is greater than ... something. Even if the $gte condition was correct, the combination of $elementMatch and $in inside doesen't do any better than regular $in:
{
skill: { $in: skills.map(item => item.tech) },
level: { $gte: ??? }
}
If you want to find candidates with tech skills of particular level or higher, it should be $or condition for each skill-level pair:
const conditions = {$or:
skills.map(s=>(
{skill: { $elemMatch: {
skill:s.skill,
level:{ $gte:s.level }
} } }
))
};

how to create index mongodb properly?

let say i have this huge documents.
2 of them got this array of object;
{
status: "A",
group: "public",
"created.dt": ....
}
{
status: "A",
group: "private",
"created.dt": ....
}
i indexed and ensure like this :
db.collection.ensureIndex({"created.dt":-1});
db.collection.ensureIndex({"created.dt":-1, "status":1});
db.collection.ensureIndex({"created.dt":-1, "group":1});
db.collection.ensureIndex({"created.dt":-1, "status":1, "group":1});
Query:
db.collection.find(
{
"status": {
$in: ["A", "I"]
},
"asset_group": "public"
},
{
sort: {
'created.dt':1
}
}
).count();
is it wrong ?
after i make this index still slow.
please help me proper index.thank you
for the following query:
db.collection.find(
{
"status": {
$in: ["A", "I"]
},
"asset_group": "public"
},
{
sort: {
'created.dt':1
}
}
).count();
The best index will be this:
db.collection.ensureIndex({"status":1, "asset_group":1, "created.dt":1});
or
db.collection.ensureIndex({"asset_group":1, "status":1, "created.dt":-1});
Since you are querying on
status, asset_group - these values can be switched in the index prefix
and sort on created.dt field - therefore created.at shuold be the last value in the index prefix. Note: On sort the index can traverse the reverse order.
For other queries, other indexes might be more suitable.
Read more about compound indexes.

MongoDB Regex $and $or Search Query

I am trying to construct a query that will accept multiple fields that can be searched over using regex for partial field matching that also has a hard constraint on other fields.
Example:
Collection: "Projects"
Required Information: { propertyId: "abc", clientId: "xyz" }
Fields to be Searched: name, serviceType.name, manager.name
Currently, I have a query like this, but if there are no results it returns all the results, which isn't helpful.
{
'$and': [
{ propertyId: '7sHGCHT4ns6z9j6BC' },
{ clientId: 'xyz' },
{ '$or':
[
{ name: /HVAC/gi },
{ 'serviceType.name': /HVAC/gi },
{ 'manager.name': /HVAC/gi }
]
}
]
}
If anyone has any insight into this it would be much appreciated.
Example Document:
{
_id: "abc",
propertyId: "7sHGCHT4ns6z9j6BC",
clientId: "xyz"
name: "16.000.001",
serviceType: {
_id: "asdf",
name: "HVAC"
},
manager: {
_id: "dfgh",
name: "Patrick Lewis",
}
}
The expected result is to only find documents where propertyId = 7sHGCHT4ns6z9j6BC AND one at least one of the following keys: name, serviceType.name, or manager.name match an inputted string, in this case, it's HVAC and if none of the regex fields match, then return nothing.
UPDATE
The issue was with MongoDB, after restarting it, everything worked.
Try following script:
db.collection.find({
$and:[
{propertyId:"7sHGCHT4ns6z9j6BC"},
{
$or:[
{name: /HVAC/i},
{"serviceType.name": /HVAC/i},
{"manager.name": /HVAC/i}
]
}]
})
Query above will return a document or documents if and only if propertyId matches and either of name, serviceType.name or manager.name matches desired regex.

MongoDB indexing multi-input search

I have a search function that looks like this:
The criteria from this search is passed through to MongoDB's find() method as a criteria object, e.g:
{
designer: "Designer1".
store: "Store1",
category: "Category1",
name: "Keyword",
gender: "Mens",
price: {$gte: 50}
}
I'm only just learning about indexes in MongoDB so please bear with me. I know I can create an index on each individual field, and I can also create a multi-index on several fields. For instance, for one index I could do:
db.products.ensureIndex({designer: 1, store: 1, category: 1, name: 1, gender: 1, price: 1})
The obvious issue arises if someone searches for, say, a category, but not a designer or store it won't be indexed.
I'm currently looking up these terms using an $and operator, so my question is:
How can I create an index that allows for this type of searching with flexibility? Do I have to create an index for each possible combination of these 6 terms? Or if I use $and in my search will it be enough to index each individual term and I'll get the best performance?
$and won't work as MongoDB can only use one index per query at the moment. So if you create an index on each field that you search on, MongoDB will select the best fitting index for that query pattern. You can try with explain() to see which one is selected.
Creating an index for each possible combination is probably not a good idea, as you'd need 6 * 5 * 4 * 3 * 2 * 1 indexes, which is 720 indexes... and you can only have 63 indexes. You could pick the most likely ones perhaps but that won't help a lot.
One solution could be to store your data differently, like:
{
properties: [
{ key: 'designer', value: "Designer1" },
{ key: 'store', value: "Store1" },
{ key: 'category', value: "Category1" },
{ key: 'name', value: "Keyword" },
{ key: 'gender', value: "Mens" },
{ key: 'price', value: 70 },
]
}
Then you can create one index on:
db.so.ensureIndex( { 'properties.key': 1, 'properties.value': 1 } );
And do searches like:
db.so.find( { $and: [
{ properties: { $elemMatch: { key: 'designer', value: 'Designer1' } } },
{ properties: { $elemMatch: { key: 'price', value: { $gte: 30 } } } }
] } )
db.so.find( { $and: [
{ properties: { $elemMatch: { key: 'price', value: { $gte: 45 } } } }
] } )
In both cases, the index is used, but only for the first part of the $and element right now. So do check which key type has the most values, and order your $and elements accordingly in the query.