Can a MongoDB query with $or be a covered query? - mongodb

I want to use an index for my query without retrieving documents ("covered query"), and my query has an $or. Is this possible?
There isn't anything in the documentation I've read to make me think this is impossible, yet my empirical testing suggests otherwise:
> db.inventory.createIndex( { quantity: 1 } )
> db.inventory.createIndex( { price: 1 } )
> db.inventory.insert({quantity:5, price: 10})
> db.inventory.insert({quantity:4, price: 20})
> db.inventory.insert({quantity:50, price: 5})
> db.inventory.find( { $or: [ { quantity: { $lt: 20 } }, { price: 5 } ] }, {_id:0,quantity:1} ).explain({verbose: "executionSummary"})["executionStats"]["totalDocsExamined"]
3
The full execution stats shows something like: PROJECTION < FETCH < OR < [IXSCAN (quantity_1), IXSCAN (price_1)]
A covered query would have 0 documents examined.
Are $or queries are really not supported as covered queries? If they aren't, can I restructure my query in a way that would be supported? Or do I need to submit a separate query for each of the $or clauses?

Let's consider a simpler query:
db.inventory.find( { $or: [ { quantity: 20 }, { price: 5 } ] }, {_id:0,price:1} ).explain({verbose: "executionSummary"})
This query is supposed to return the price for all of the documents where quantity is 20. But, you are also asking it to use the quantity index to match the documents where quantity is 20. How is it supposed to obtain the price for the documents where quantity is 20, only using the quantity index?

Related

What is the best practice to find mongo documents count?

Wanted to know the performance difference between countDocument and find query.
I have to find the count of documents based on certain filter, which approach will be better and takes less time?
db.collection.countDocuments({ userId: 12 })
or
db.collection.find({ userId: 12 }) and then using the length of resulted array.
You should definitely use db.collection.countDocuments() if you don't need the data. This method uses an aggregation pipeline with the filters you pass on and only returns the count so you don't waste processing and time waiting for an array with all results.
This:
db.collection.countDocuments({ userId: 12 })
Is equivalent to:
db.collection.aggregate([
{ $match: { userId: 12 } },
{ $group: { _id: null, n: { $sum: 1 } } }
])

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 }
} } }
))
};

Mongoose: Score query then sort by score - non text fields

In my db, I have a collection of books.
Each have:
a count of upvotes
a count of downvotes
a count of views
I would like to sort my db by scoring as follows:
upvote: 8 points
downvote: -4 points
view: 1/2 point
So the score will be:
(NumberOfViews*(1/2)) + (NumberOfDownvotes*-4)+ (NumberOfUpvotes*8)
So if I have:
book1 = {name:'book1', views:3000,upvotes:340, downvotes:120}
book2 = {name:'book2', views:9000,upvotes:210, downvotes:620}
book3 = {name:'book3', views:7000,upvotes:6010, downvotes:2}
The score should be:
book1Score = 3740
book2Score = 3700
book3Score = 51572
And the query should output
book3,book1,book2
How can I achieve such a thing in mongoose?
Bonus: What if I want records that are more recent to rank higher than older records on that same query?
Thanks
Well I ended up doing it all inside mongoose.
I run this query every 24 hours to re-score my collection.
Book.aggregate(
[
//I match my query
{$match:query},
{
$project: {
//take the id for reference
_id: 1,
//calculate the score of the views
viewScore: {
$multiply: [ "$views", 0.5 ]
},
//calculate the score of the upvotes
upvoteScore: {
$multiply: [ {$size: '$upvotes'}, 8 ]
},
//calculate the score of the downvotes
downvoteScore: {
$multiply: [ {$size: '$downvotes'}, -4 ]
}
}
},
{
//project a second time
$project: {
//take my id for reference
_id: 1,
//get my total score
score: {
$add:['$viewScore','$upvoteScore','$downvoteScore']
},
}
},
//sort by the score.
{$sort : {'score' : -1}},
]
)
I think the best way would be to query mongoose for the list of book then do the sorting yourself.
Something like:
// Get query results from mongoose then ...
books.sort((a,b) => {
return ((a.views*(1/2))+(a.downvotes*-4)+(a.upvotes*8))-((b.view*(1/2))+ b.downvotes*-4)+(b.upvotes*8))
});
This would sort the books in ascending order of highest points
EDIT: The above answer is for sorting after you've received the query. (And also just realized you want descending for above^ so just switch the placement to be b - a)
If you want to receive the query already sorted, you could instead calculate the score at the time you input the book and add that as a field. The use mongoose's Query#sort. Which would look something like
query.sort({ score: 'desc'});
More info on Query#sort: http://mongoosejs.com/docs/api.html#query_Query-sort

Filter large dataset base on aggregation result

I need to do sort of an "Advanced Search" functionality with MongoDB. It's a sport system, where player statistic are collected for each season like this:
{
player: {
id: int,
name: string
},
goals: int,
season: int
}
Uses can search data across season, for example: I want to search for player who scored > 30 goals from season 2012 - 2016.
I could use mongodb aggregation:
db.stats.aggregate( [
{ $match: { season: { $gte: 2014, $lte: 2016 } } }
{ $group: { _id: "$player", totalGoals: { $sum: "$goals" } } },
{ $match: { $totalGoals: { $gte: 30 } } },
{ $limit: 10 },
{ $skip: 0 }
] )
That's working fine, the speed is acceptable for the collections with more than 3 millions records.
However, if the user just want to search for a larger seasons range, let say: players lifetime statistic. The aggregation turns out to be very very very slow. And I understand that MongoDB has to go through all the docs and calculate the $totalGoals.
I just wonder if there is better approach that could solve this performance problem?
you can have pre-calculated data for past seasons and make two step query:
a) get past data
b) get current data
you could try to optimise indexes on that query
hardware: use SSD
hardware: more memory
introduce sharding to split load

MongoDB : How to search based on two Parameters in Mongo

I have a Collection named as Orders in my Mongo DB .
Is it possible to do a search on mongo DB based on two fileds .
For example i want to search the collection based on symbol and hi
db.Orders.find({"symbol" : "AADI"})
Please let me know how can i include the other parameter hi also in the search ??
Thanks
Just have two conditions separated by commas:
e.g.
db.users.find(
{ status: "A",
age: 50 }
)
http://docs.mongodb.org/manual/reference/sql-comparison/
Mongodb provides an implicit AND , so when you do
db.inventory.find( { price: 1.99, qty: { $lt: 20 } , sale: true } )
it is same as
db.inventory.find({ $and: [ { price: 1.99 }, { qty: { $lt: 20 } }, { sale: true } ] } )
For other operators you can have a look at the reference
mixing $or with logical AND
db.inventory.find( { price:1.99, $or: [ { qty: { $lt: 20 } }, { sale: true } ] } )
This query will select all documents in the inventory collection where:
the price field value equals 1.99 and
either the qty field value is less than 20 or the sale field value is true.
Other operators
You can refer to the reference for examples to other operators.
Also, once you use:
db.users.find(
{ status: "A",
age: 50 }).pretty()
Using pretty() method at the end of the statement, you make sure that the info comes with a \n per key : value