MongoDB $and on two columns - mongodb

I am following MongoDB documentation for C# & shell commands to understand find() for $and for restaurants example. However, following issue I observed:
ar builder = Builders<BsonDocument>.Filter;
var filter = builder.Eq("cuisine", "Italian") & builder.Eq("address.zipcode", "10075");
filters the restaurants collection however when I change filter as:
var filter = builder.Eq("borough", "Manhattan") & builder.Eq("cuisine", "American");
This doesn't return any record as I can see combination of these two are present in restaurants collection.
Shell command which is not working:
db.restaurants.find( { $and: [ {borough: "Queens" }, { cuisine: "American" } ] } )
Any clue?

Yo don't need to use the $and operator in this case.
db.restaurants.find({borough: "Queens", cuisine: "American"})
Only use $and operator when you need to specify more than one condition for the same field (i.e: db.restaurants.find( { $and: [ {score:{$gte:5}}, {score:{$lte:10}} ] } ))

Related

Use $in operator to match regex in mongoengine

Following the MongoDB documentation, you can use the $in operator with a regular expression like wise db.inventory.find( { tags: { $in: [ /^be/, /^st/ ] } } ). Is there a way to achieve the same result using mongoengine?
For example pass {"tags__in": ["/^be/", "/^st/"]} to my query?
I don't think this is supported by MongoEngine in normal query construct (i.e I doubt that Doc.objects(tags__in=["/^be/", "/^st/"] will work)) but there is support for __raw__ query (https://docs.mongoengine.org/guide/querying.html#raw-queries)
so the following should work
Inventory.objects(__raw__={"tags": { "$in":["/^be/", "/^st/"]}})

Within a mongodb $match, how to test for field MATCHING , rather than field EQUALLING

Can anyone tell me how to add a $match stage to an aggregation pipeline to filter for where a field MATCHES a query, (and may have other data in it too), rather than limiting results to entries where the field EQUALS the query?
The query specification...
var query = {hello:"world"};
...can be used to retrieve the following documents using the find() operation of MongoDb's native node driver, where the query 'map' is interpreted as a match...
{hello:"world"}
{hello:"world", extra:"data"}
...like...
collection.find(query);
The same query map can also be interpreted as a match when used with $elemMatch to retrieve documents with matching entries contained in arrays like these documents...
{
greetings:[
{hello:"world"},
]
}
{
greetings:[
{hello:"world", extra:"data"},
]
}
{
greetings:[
{hello:"world"},
{aloha:"mars"},
]
}
...using an invocation like [PIPELINE1] ...
collection.aggregate([
{$match:{greetings:{$elemMatch:query}}},
]).toArray()
However, trying to get a list of the matching greetings with unwind [PIPELINE2] ...
collection.aggregate([
{$match:{greetings:{$elemMatch:query}}},
{$unwind:"$greetings"},
]).toArray()
...produces all the array entries inside the documents with any matching entries, including the entries which don't match (simplified result)...
[
{greetings:{hello:"world"}},
{greetings:{hello:"world", extra:"data"}},
{greetings:{hello:"world"}},
{greetings:{aloha:"mars"}},
]
I have been trying to add a second match stage, but I was surprised to find that it limited results only to those where the greetings field EQUALS the query, rather than where it MATCHES the query [PIPELINE3].
collection.aggregate([
{$match:{greetings:{$elemMatch:query}}},
{$unwind:"$greetings"},
{$match:{greetings:query}},
]).toArray()
Unfortunately PIPELINE3 produces only the following entries, excluding the matching hello world entry with the extra:"data", since that entry is not strictly 'equal' to the query (simplified result)...
[
{greetings:{hello:"world"}},
{greetings:{hello:"world"}},
]
...where what I need as the result is rather...
[
{greetings:{hello:"world"}},
{greetings:{hello:"world"}},
{greetings:{"hello":"world","extra":"data"}
]
How can I add a second $match stage to PIPELINE2, to filter for where the greetings field MATCHES the query, (and may have other data in it too), rather than limiting results to entries where the greetings field EQUALS the query?
What you're seeing in the results is correct. Your approach is a bit wrong. If you want the results you're expecting, then you should use this approach:
collection.aggregate([
{$match:{greetings:{$elemMatch:query}}},
{$unwind:"$greetings"},
{$match:{"greetings.hello":"world"}},
]).toArray()
With this, you should get the following output:
[
{greetings:{hello:"world"}},
{greetings:{hello:"world"}},
{greetings:{"hello":"world","extra":"data"}
]
Whenever you're using aggregation in MongoDB and want to create an aggregation pipeline that yields documents you expect, you should always start your query with the first stage. And then eventually add stages to monitor the outputs from subsequent stages.
The output of your $unwind stage would be:
[{
greetings:{hello:"world"}
},
{
greetings:{hello:"world", extra:"data"}
},
{
greetings:{hello:"world"}
},
{
greetings:{aloha:"mars"}
}]
Now if we include the third stage that you used, then it would match for greetings key that have a value {hello:"world"} and with that exact value, it would find only two documents in the pipeline. So you would only be getting:
{ "greetings" : { "hello" : "world" } }
{ "greetings" : { "hello" : "world" } }

Mongo $in query with case-insensitivity

I'm using Mongoose.js to perform an $in query, like so:
userModel.find({
'twitter_username': {
$in: friends
}
})
friends is just an array of strings. However, I'm having some case issues, and wondering if I can use Mongo's $regex functionality to make this $in query case-insensitive?
From the docs:
To include a regular expression in an $in query expression, you can
only use JavaScript regular expression objects (i.e. /pattern/ ). For
example:
{ name: { $in: [ /^acme/i, /^ack/ ] } }
One way is to create regular Expression for each Match and form the friends array.
var friends = [/^name1$/i,/^name2$/i];
or,
var friends = [/^(name1|name2)$/i]
userModel.find({"twitter_username":{$in:friends }})
Its a little tricky to do something like that
at first you should convert friends to new regex array list with:
var insesitiveFriends = [];
friends.forEach(function(item)
{
var re = new RegExp(item, "i");
insesitiveFriends.push(re);
})
then run the query
db.test.find(
{
'twitter_username':
{
$in: insesitiveFriends
}
})
I have the sample documents in test collection
/* 0 */
{
"_id" : ObjectId("5485e2111bb8a63952bc933d"),
"twitter_username" : "David"
}
/* 1 */
{
"_id" : ObjectId("5485e2111bb8a63952bc933e"),
"twitter_username" : "david"
}
and with var friends = ['DAvid','bob']; I got both documents
Sadly, mongodb tends to be pretty case-sensitive at the core. You have a couple of options:
1) Create a separate field that is a lowercase'd version of the twitter_username, and index that one instead. Your object would have twitter_username and twitter_username_lc. The non-lowerecase one you can use for display etc, but the lowercase one you index and use in your where clause etc.
This is the route I chose to go for my application.
2) Create a really ugly regex from your string of usernames in a loop prior to your find, then pass it in:
db.users.find({handle:{$regex: /^benhowdle89|^will shaver|^superman/i } })
Note that using the 'starts with' ^ carrot performs better if the field is indexed.

MongoDB: Search in array

I have a field in a MongoDB Collection like:
{
place = ['London','Paris','New York']
}
I need a query that will return only that particular entry of the array, where a specific character occurs. For Example, I want to search for the terms having the letter 'o'(case-insensitive) in them. It should just return 'London' and 'New York'.
I tried db.cities.find({"place":/o/i}), but it returns the whole array.
You'll need to $unwind using an aggregate query, then match.
db.cities.aggregate([ { $unwind:'$place' }, { $match: { place : {$regex: /o/i } } } ])
In simple you find with $regex below query will work without aggregation
db.collectionName.find({ place : {$regex: /o/i } })

case-insensitive query on mongodb

is there a way to query for a case-insensitive value on mongo without using map/reduce?
Suppose you have document that contains tag field and you want search on it
Tags
{
tag,
...
}
First option is use regex(but it work slow as #RestRisiko said):
db.tags.find( { "tag" : { "$regex" : "C#", "$options" : "-i" } })
Second option is create another, lower case field( and in mongodb it best way):
Tags
{
tag,
tagLower,
..
}
And use find as usual:
db.tags.find( { "tagLower" : "c#"})
It will work faster, because above code can use index for search.
You have to normalize the data to be queried. Using a regular expression for case-insensitive search might work as well it won't use indexes. So your only option is to normalize. If you need to preserve the original state then you need to denormalize the data and store the normalized values in a dedicated column of the document.
When using it with Node.js, it's best to build a RegEx object in the query.
Room.findOne({'name': new RegExp(roomName, 'i')}, {}, function(err, room) {
...
Use regular expressions matching as below. The 'i' shows case insensitivity.
var collections = mongoDatabase.GetCollection("Abcd");
var queryA = Query.And(
Query.Matches("strName", new BsonRegularExpression("MSID", "i")),
Query.Matches("strVal", new BsonRegularExpression("154800", "i")));
var queryB = Query.And(
Query.Matches("strName", new BsonRegularExpression("Operation","i")),
Query.Matches("strVal", new BsonRegularExpression("8221", "i")));
var getA = collections.Find(queryA);
var getB = collections.Find(queryB);
If there are some special characters in the query, regex simple will not work. You will need to escape those special characters.
The following helper function can help without installing any third-party library:
const escapeSpecialChars = (str) => {
return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
And your query will be like this:
db.collection.find({ field: { $regex: escapeSpecialChars(query), $options: "i" }})
OR
db.collection.find({ field: { $regex: `.*${escapeSpecialChars(query)}.*`, $options: "i" }})
Hope it will help!
With MongoDB v3.6+, you can chain up $toLower/$toUpper with $expr to perform case-insensitive search.
db.collection.find({
$expr: {
$eq: [
{
$toLower: "$value"
},
{
$toLower: "apple"// your search string here
}
]
}
})
Here is the Mongo Playground for your reference.