Optimized way of Querying in MongoDB using $in vs $or - mongodb

Implementing an application that looks up a table for mail id presence from a list of around 10 email ids. Tried using $or and $in.
$in seems to give better performance but not significant. Is one more optimized than other?

MongoDB docs have the answer:
"When using $or with <expressions> that are equality checks for the value of the same field, choose the $in operator over the $or operator."

$or operator is logical operator where you can define your own login but $in operator is Comparison operator where you can compare you can not put your on logic.
Syntax of $in:
{ field: { $in: [<value1>, <value2>, ... <valueN> ] } }
Example:
db.account.find( { qty: { $in: [ 5, 15 ] } } )
Syntax of $or:
{ $or: [ { <expression1> }, { <expression2> }, ... , { <expressionN> } ] }
Example:
db.account.find( { $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] } )
Note: Account is your collection name

"While "$or"will always work, use "$in"whenever possible as the query optimizer
handles it more efficiently."
Moreover "$in" has more readability.
Ref: MongoDB: The Definitive Guide

Well that will insure no indecis to be ensured if you use $in, however i prefer to format it to $or as it will ensure index (readability won't concern me at is being handled in application logic in which i prefer to consume the memory of app rather than mongodb server)

Related

MongoDB $or operator for multiple fields separately

Looks like MongoDB supports $or operator only in the query root, and not per field query. So this doesn't work:
db.collection.find({
foo: {
$or: [ fooQuery1, fooQuery2 ]
},
bar: {
$or: [ barQuery1, barQuery2 ]
}
})
so we have to write it like:
db.collection.find({
$or: [
{ foo: fooQuery1, bar: barQuery1 },
{ foo: fooQuery1, bar: barQuery2 },
{ foo: fooQuery2, bar: barQuery1 },
{ foo: fooQuery1, bar: barQuery2 },
]
})
Basically write all possible combinations for foo and bar. But this is insane when we have to query by >2 fields with >2 OR-statements each.
It is quite easy to write a function which takes the fields and its OR statements from the first example and to generate all possible variations from the second example. But is there some MongoDB native approach, may be we are missing here something.
And may be you know the reason, why the first approach is not supported? So that we better understand mongodb internals.
Thank you.
There are a couple of ways this query could be written, but it depends on the actual operations contained in each query.
If the queries are checking equality or a regular expression, you could use the $in operator, like
{
foo: { $in: [ "fooValue1", /^fooPrefix/ ]},
bar: { $in: [ "barValue1", "barValue2" ]}
}
If the subqueries have other tests, like inequality or existence, you could combine each field's queries in a separate $or, with an $and operator to ensure a match from each one, like:
{$and: [
{$or: [{foo: fooQuery1}, {foo: fooQuery2}]},
{$or: [{bar: barQuery1}, {bar: barQuery2}]}
]}

Query mongodb array of documents

I've a bunch of documents that look like:
{
"ids": [{"name":"aa", "age":1}, {"name":"bb", "age":2}]
}
I'd like to be able to query my documents providing a collection of ids, something like
db.getCollection('Collection').find({"ids":{$in : [{"name":"aa", "age":1}, {"name":"bb", "age":2}]}})
Generally that works, however it breaks when the fields order is changed, so for example I cannot find documents when I execute the following query
db.getCollection('Collection').find({"ids":{$in : [{"age":1,"name":"aa"}, { "age":2, "name":"bb"}]}})
I know that I could try to always execute a query with fields "in order", but from my current task perspective it's not always possible. Any help with that ?
You need $elemMatch when you want to run your query against an array of objects:
db.col.find({ $or: [ { "ids": { $elemMatch: {"age":1,"name":"aa"} } }, { "ids": { $elemMatch: { "age":2, "name":"bb"} } } ] })
Mongo Playground
EDIT: you can decide whether $or or $and should be a top level operator (depending on your use case)

How to add order in $all operator in MongoDB

How can I get documents from mongo with an array containing some elements but IN THE SAME ORDER?
I know that $all do the job but ignoring the order of elements. The order in my case is important and I can't sort my arrays since it's describing a path that I want to keep the order.
111,222,333 is not the same as 222,111,333
Is there a way to do it using $all or maybe another operator in mongo aggregation framework?
You can avoid the first "intersect" field, is just to give you back as debug what MongoDB make with this command. You should create the $and operator dynamically.
db.Test6.aggregate([
{
$project: {
_id:1,
pages:1,
intersect: {$setIntersection: [[111,666], "$pages"]},
theCondition: {$let: {
vars: {
intersect: {$setIntersection: [[111,666], "$pages"]}
},
in: {
$cond:[ {$and:[
{$eq:[{$arrayElemAt:["$$intersect", 0]}, 111]},
{$eq:[{$arrayElemAt:["$$intersect", 1]}, 666]}
]} , true, false]
}
}
}
}
}
]);

Mongodb query - apply condition to subfield only when field exists

I need to find a document either when a field doesn't exist or when a subfield of this field meets some condition.
A similar question was asked here: Mongodb query - apply condition only if field exists. I'll use the code from this answer to illustrate:
db.stackoverflow.find({
$or: [
{ howmuch: { $exists:false } },
{ 'howmuch.chocolate':5 }
]})
Of course when I do that I get an error when howmuch is undefined. I know I could test if 'howmuch.chocolate' exists but that wouldn't change anything. Is there a way to do that?
This should work:
db.stackoverflow.find({
$or: [
{ howmuch: { $exists:false } },
{ $and: [{ 'howmuch.chocolate': { $exists: true } } ,{ 'howmuch.chocolate':5 }]}
]})
According to the documentation, if the first expression in the $and array evaluates to false, the remaining expressions are not evaluated.

Getting first and last element of array in MongoDB

Mongo DB: I'm looking to make one query to return both the first and last element of an array. I realize that I can do this multiple queries, but I would really like to do it with one.
Assume a collection "test" where each objects has an array "arr" of numbers:
db.test.find({},{arr:{$slice: -1},arr:{$slice: 1}});
This will result in the following:
{ "_id" : ObjectId("xxx"), "arr" : [ 1 ] } <-- 1 is the first element
Is there a way to maybe alias the results? Similar to what the mysql AS keyword would allow in a query?
This is not possible at the moment but will be with the Aggregation Framework that's in development now if I understand your functional requirement correctly.
You have to wonder about your schema if you have this requirement in the first place though. Are you sure there isn't a more elegant way to get this to work by changing your schema accordingly?
This can be done with the aggregation framework using the operators $first and $last as follows:
db.test.aggregate([
{ '$addFields': {
'firstElem': { '$first': '$arr' },
'lastElem': { '$last': '$arr' }
} }
])
or using $slice as
db.test.aggregate([
{ '$addFields': {
'firstElem': { '$slice': [ '$arr', 1 ] },
'lastElem': { '$slice': [ '$arr', -1 ] }
} }
])