Search inside a range of fields MongoDB - mongodb

I have a DB on MongoDB, with a collection like this:
{
"_id" : ObjectId("59a64b4cfb80146432aff6ac"),
"name": "Mid Range",
"end" : NumberLong("50000"),
"start" : NumberLong("10000"),
},
{
"_id" : ObjectId("59a64b4cfb80146432aff6ac"),
"name": "Hi Range",
"end" : NumberLong("100000"),
"start" : NumberLong("150000"),
}
The user enters a number to validate: 125000, i need a query to get: "Hi Range" document.
How can i do that? I'm trying to avoid code side, but if there is no choice it's ok.
Thanks!

You could even make do without the $and. You just need to set the query to find a result such that your number is less than or equal to its end and greater than or equal to its start. For example:
db.collection.find({start: {$lte: 125000}, end: {$gte: 125000}})
Note: Careful, if you want that range to include the start and end number, use $lte, $gte. Using $lt or $gt will not include it.

You can do it like this -
db.find({name:/Hi Range/}) // regular Expression.

To avoid logic that determine that 12500 is Hi Range you could do something like this
db.collection.find( { $and: [ { start: { $lte: 125000 } }, { end: { $gt: 12500 } } ] } )

Related

How to correctly perform join but with math operations in MongoDB?

Here I have a collection, say test, storing data with a field named timestamp (in ms). Documents in this collection are densely inserted with timestamp interval 60000. That's to say, I can always find one and only one document whose timestamp is 1 minute before that of a refered one (except for the very first one, of course). Now I want to perform a join to correlate each document with that whose timestamp is 1 minute before. I've tried this aggregation:
...
$lookup : {
from: 'test',
let : { lastTimestamp: '$timestamp'-60000 },
pipeline : [
{$match : {timestamp:'$timestamp'}}
],
as: 'lastObjArr'
},
...
which intends to find the array of the very document and set it as the value of key lastObjArr. But in fact lastObjArr is always an empty one. What happend?
Your $lookup pipeline is incomplete as it's missing the necessary math operators. For a start, lastObjArr is empty due to a number of factors, one of them being that the expression
let : { lastTimestamp: '$timestamp'-60000 },
doesn't evaluate correctly, it needs to use the $subtract operator
let : { lastTimestamp: { $subtract: ['$timestamp', 60000] } },
Also, the $match pipeline step needs to use the $expr operator together with $eq for the query to work, i.e.
$lookup : {
from: 'test',
let : { lastTimestamp: { $subtract: ['$timestamp', 60000] } },
pipeline : [
{ $match : {
$expr: { $eq: ['$timestamp', '$$lastTimestamp'] }
} }
],
as: 'lastObjArr'
}
you defined a variable called "lastTimestamp" and you assign it with
'$timestamp'-60000
But you never use it, change your code as following it should work:
$lookup : {
from: 'test',
let : { lastTimestamp: '$timestamp'-60000 },
pipeline : [
{$match : {timestamp:'$$lastTimestamp'}}
],
as: 'lastObjArr'
},

Need for using $and

In MongoDB, I have this following code:
db.products.find({name: "Postcard", status: "Available"})
But isn't that the same as using $and? If not, what is the difference?
Another example...
Where the status equals "Available" and either qty is less than ($gt) 100 or name starts with the characters "Po":
db.products.find( {status:"Available", $or:[{qty:{$gt:100 }},{item:/^Po/}]})
So seems as if there is no need of using $and in these two examples. So why or when would $and be used?
In both your examples it is superfluous to use $and because using ',' to specify match conditions on several different fields accomplishes it just the same.
One instance when to use them is if you need to specify multiple conditions on the same field. Here is an example (straight from mongodb tutorial videos).
db.movieDetails.find({"$and": [{"metacritic": {"$ne": "null"}},
"metacritic": {"$exists": "true"}]})
The explanation provided was that the keys in a JSON document must be unique. So if the above query were to be specified without $and, only the last "metacritic" value would be apparently be used.
Mongodb documentation specifies another example listed with a similar explanation. Notice $or operator being specified twice.
db.inventory.find( {
$and : [
{ $or : [ { price : 0.99 }, { price : 1.99 } ] },
{ $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
]
} )

Select data where the range between two different fields contains a given number

I want to make a find query on my database for documents that have an input value between or equal to these 2 fields, LOC_CEP_INI and LOC_CEP_FIM
Example: user input a number to the system with value : 69923994, then I use this input to search my database for all documents that have this value between the range of the fields LOC_CEP_INI and LOC_CEP_FIM.
One of my documents (in this example this document is selected by the query because the input is inside the range):
{
"_id" : ObjectId("570d57de457405a61b183ac6"),
"LOC_CEP_FIM" : 69923999, //this field is number
"LOC_CEP_INI" : 69900001, // this field is number
"LOC_NO" : "RIO BRANCO",
"LOC_NU" : "00000016",
"MUN_NU" : "1200401",
"UFE_SG" : "AC",
"create_date" : ISODate("2016-04-12T20:17:34.397Z"),
"__v" : 0
}
db.collection.find( { field: { $gt: value1, $lt: value2 } } );
https://docs.mongodb.com/v3.2/reference/method/db.collection.find/
refer this mongo provide range facility with $gt and $lt .
You have to invert your field names and query value.
db.zipcodes.find({
LOC_CEP_INI: {$gte: 69923997},
LOC_CEP_FIM: {$lte: 69923997}
});
For your query example to work, you would need your documents to hold an array property, and that each item in this prop hold a 69923997 prop. Mongo would then check that this 69923997 prop has a value that is both between "LOC_CEP_INI" and "LOC_CEP_FIM" for each item in your array prop.
Also I'm not sure whether you want LOC_CEP_INI <= 69923997 <= LOC_CEP_FIM or the contrary, so you might need to switch the $gte and $lte conditions.
db.zipcodes.find( {
"LOC_CEP_INI": { "$lte": 69900002 },
"LOC_CEP_FIM": { "$gte": 69900002 } })
Here is the logic use it as per the need:
Userdb.aggregate([
{ "$match": { _id: ObjectId(session._id)}},
{ $project: {
checkout_list: {
$filter: {
input: "$checkout_list",
as: "checkout_list",
cond: {
$and: [
{ $gte: [ "$$checkout_list.createdAt", new Date(date1) ] },
{ $lt: [ "$$checkout_list.createdAt", new Date(date2) ] }
]
}
}
}
}
}
Here i use filter, because of some reason data query on nested data is not gets succeed in mongodb

mongodb: document with the maximum number of matched targets

I need help to solve the following issue. My collection has a "targets" field.
Each user can have 0 or more targets.
When I run my query I'd like to retrieve the document with the maximum number of matched targets.
Ex:
documents=[{
targets:{
"cluster":"01",
}
},{
targets:{
"cluster":"01",
"env":"DC",
"core":"PO"
}
},{
targets:{
"cluster":"01",
"env":"DC",
"core":"PO",
"platform":"IG"
}
}];
userTarget={
"cluster":"01",
"env":"DC",
"core":"PO"
}
You seem to be asking to return the document where the most conditions were met, and possibly not all conditions. The basic process is an $or query to return the documents that can match either of the conditions. Then you basically need a statement to calculate "how many terms" were met in the document, and return the one that matched the most.
So the combination here is an .aggregate() statement using the intitial results from $or to calculate and then sort the results:
// initial targets object
var userTarget = {
"cluster":"01",
"env":"DC",
"core":"PO"
};
// Convert to $or condition
// and the calcuation condition to match
var orCondition = [],
scoreCondition = []
Object.keys(userTarget).forEach(function(key) {
var query = {},
cond = { "$cond": [{ "$eq": ["$target." + key, userTarget[key]] },1,0] };
query["target." + key] = userTarget[key];
orCondition.push(query);
scoreCondition.push(cond);
});
// Run aggregation
Model.aggregate(
[
// Match with condition
{ "$match": { "$or": orCondition } },
// Calculate a "score" based on matched fields
{ "$project": {
"target": 1,
"score": {
"$add": scoreCondition
}
}},
// Sort on the greatest "score" (descending)
{ "$sort": { "score": -1 } },
// Return the first document
{ "$limit": 1 }
],
function(err,result) {
// check errors
// Remember that result is an array, even if limitted to one document
console.log(result[0]);
}
)
So before processing the aggregate statement, we are going to generate the dynamic parts of the pipeline operations based on the input in the userTarget object. This would produce an orCondition like this:
{ "$match": {
"$or": [
{ "target.cluster" : "01" },
{ "target.env" : "DC" },
{ "target.core" : "PO" }
]
}}
And the scoreCondition would expand to a coding like this:
"score": {
"$add": [
{ "$cond": [{ "$eq": [ "$target.cluster", "01" ] },1,0] },
{ "$cond": [{ "$eq": [ "$target.env", "DC" ] },1,0] },
{ "$cond": [{ "$eq": [ "$target.core", "PO" ] },1,0] },
]
}
Those are going to be used in the selection of possible documents and then for counting the terms that could match. In particular the "score" is made by evaluating each condition within the $cond ternary operator, and then either attributing a score of 1 where there was a match, or 0 where there was not a match on that field.
If desired, it would be simple to alter the logic to assign a higher "weight" to each field with a different value going towards the score depending on the deemed importance of the match. At any rate, you simply $add these score results together for each field for the overall "score".
Then it is just a simple matter of applying the $sort to the returned "score", and then using $limit to just return the top document.
It's not super efficient, since even though there is a match for all three conditions the basic question you are asking of the data cannot presume that there is, hence it needs to look at all data where "at least one" condition was a match, and then just work out the "best match" from those possible results.
Ideally, I would personally run an additional query "first" to see if all three conditions were met, and if not then look for the other cases. That still is two separate queries, and would be different from simply just pushing the "and" conditions for all fields as the first statement in $or.
So the preferred implementation I think should be:
Look for a document that matches all given field values; if not then
Run the either/or on every field and count the condition matches.
That way, if all fields match then the first query is fastest and only needs to fall back to the slower but required implementaion shown in the listing if there was no actual result.

Query returns more than expected results

Bear with me, this is not really my question. Just trying to get someone to understand.
Authors note:
The possible duplicate question solution allows $elemMatch to constrain because >all of the elements are an array. This is a little different.
So, in the accepted answer the main point has been brought up. This behavior is well
documented and you should not "compare 'apples'` with 'oranges'". The fields are of
different types, and while there is a workaround for this, the best solution for the real
world is don't do this.
Happy reading :)
I have a collection of documents I am trying to search, the collection contains the following:
{ "_id" : ObjectId("52faa8a695fa10cc7d2b7908"), "x" : 1 }
{ "_id" : ObjectId("52faa8ab95fa10cc7d2b7909"), "x" : 5 }
{ "_id" : ObjectId("52faa8ad95fa10cc7d2b790a"), "x" : 15 }
{ "_id" : ObjectId("52faa8b095fa10cc7d2b790b"), "x" : 25 }
{ "_id" : ObjectId("52faa8b795fa10cc7d2b790c"), "x" : [ 5, 25 ] }
So I want to find the results where x falls between the values of 10 and 20. So this is the query that seemed logical to me:
db.collection.find({ x: {$gt: 10, $lt: 20} })
But the problem is this returns two documents in the result:
{ "_id" : ObjectId("52faa8ad95fa10cc7d2b790a"), "x" : 15 }
{ "_id" : ObjectId("52faa8b795fa10cc7d2b790c"), "x" : [ 5, 25 ] }
I am not expecting to see the second result as none of the values are between 10 and 20.
Can someone explain why I do not get the result I expect? I think { "x": 15 } should be the only document returned.
So furthermore, how can I get what I expect?
This behaviour is expected and explained in mongo documentation here.
Query a Field that Contains an Array
If a field contains an array and your query has multiple conditional
operators, the field as a whole will match if either a single array
element meets the conditions or a combination of array elements
meet the conditions.
Mongo seems to be willing to play "smug", by giving back results when a combination of array elements match all conditions independently.
In our example, 5 matches the $lt:20 condition and 25 matches the $gt:10 condition. So, it's a match.
Both of the following will return the [5,25] result:
db.collection.find({ x: {$gt: 10, $lt: 20} })
db.collection.find({ $and : [{x: {$gt: 10}},{x:{ $lt: 20}} ] })
If this is user expected behaviour, opinions can vary. But it certainly is documented, and should be expected.
Edit, for Neil's sadistic yet highly educational edit to original answer, asking for a solution:
Use of the $elemMatch can make "stricter" element comparisons for arrays only.
db.collection.find({ x: { $elemMatch:{ $gt:10, $lt:20 } } })
Note: this will match both x:[11,12] and x:[11,25]
I believe when a query like this is needed, a combination on two queries is required, and the results combined. Below is a query that returns correct results for documents with x being not an array:
db.collection.find( { $where : "!Array.isArray(this.x)", x: {$gt: 10, $lt: 20} } )
But the best approach in this case is to change the type of x to always be an array, even when it only contains one element. Then, only the $elemMatch query is required to get correct results, with expected behaviour.
You can first check if the subdocument is not and array and provide a filter for the desired values:
db.collection.find(
{
$and :
[
{ $where : "!Array.isArray(this.x)" },
{ x: { $gt: 10, $lt: 20 } }
]
}
)
which returns:
{ "_id" : ObjectId("52fb4ec1cfe34ac4b9bab163"), "x" : 15 }