match all if the user doesn't specify the field value MongoDB - node.js

I am building an API where I have several fields that are optional in my get request. So I want MongoDB to match all values for those optional fields if the user does not specify it. I have come up with this solution:
db.collection(expenses_collection).find(username: username, category: {$regex:"/" + category + "/"}, payment_type: {$regex:"/" + payment_type + "/"}})
Where if category and payment_type are not specified by the user I set them to ".*":
const {category=".*", payment_type=".*"} = req.query;
However, mongodb is still not matching any data. Any help is appreciated. Thanks a lot.

The issue is with your regex string. To match any string value, you have to use this pattern (this matches any string): (.*?)
Consider input documents:
{ _id: 1, name: "John", category: "cat 1", payment_type: "cash" },
{ _id: 2, name: "Jane", category: "cat 2", payment_type: "credit card" }
Usage to match any category field value:
let categoryStr = /(.*?)/
db.exp.find( { category: categoryStr } )
The query returns all documents.
So, in your application for the category value not specified the code can be like this:
if (category is empty or null) { // category not specified by user
categoryStr = /(.*?)/
}
Similarly, for the payment_type field also.
Then query would be:
db.exp.find( {
username: usernameStr,
category: categoryStr,
payment_type: paymentStr
} )
NOTE: The code tests fine with MongoDB NodeJS driver APIs.

Isn't this what exists is made for?
{category: { $exists: true }, payment_type: { $exists: true }}

Related

How to find text from populated model in mongoose?

We have two models:
User
const userSchema = new mongoose.Schema({
name: {
type: String
},
email: {
type: String
},
vehicleId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'vehicle'
}
})
Vehicle
const vehicleSchema = new mongoose.Schema({
name: {
type: String
},
color: {
type: String
}
})
Now, we have to find user input from user's ( name, email ) & also from vehicle's ( name, color )
- If user search "pink" then we have to find that if any user has with name, email or them vehicle include "pink" keyword or not.
- If string match from reference model then we have to return whole record.
Can you please help us, how to achieve this?
You can first run a query on a Vehicle model to fetch all vehicles that have name pink
let vehicleIds=await Model.Vehicle.aggregate([
{$match:{$or:[
{name:{$regex:"pink",$options:"i"}},
{color:{$regex:"pink",$options:"i"}
]}},
{$group:{_id:null,ids:{$push:"$_id"}}}
])
here if vehicleIds is not empty, then vehicles[0].ids will have vehcileIds that have pink in name or color
let vehcileIds=[]
if(vehicles.length) vehcileIds=vehicles[0].ids
after above query runs run another query in the user model
let users= await Models.User.find({$or:[
{name:{$regex:"pink",$options:"i"}},
{email:{$regex:"pink",$options:"i"}
{vehicleId:{$in:}}
]})
I think, the best thing you can do is, Text Search.
ANSWER 1:
1.1. Step one
You have to create text index on your collection to perform text search.
create text index with
db.user.createIndex({"name":"text", "email": "text"})
db.vehicle.createIndex({"name": "text", "color": "text"})
we have to create text index on both collection as we want to perform text search on both collections.
*
Note:
as we are creating a text index on "name", "email" from User & "name", "color" from Vehicle, you can only perform text search on this four fields in respective collections.
you can assign weights to fields so that you can sort your result according to text score. (you must do this while creating index).
*
1.2. Step two
(i am assuming you are using Javascript and mongoose here.)
db.collection("users").aggregate({$match:{$text: {$search: "pink"}}},
(err, userData: any)=>{
if(err){
return err;
}
if(userdata.length){
let vehicleIds = userData.map((user) => mongoose.Types.ObjectId(user.vehicleId));
db.collection("vehicles").aggregate({$match:{$text:{$search:"pink", _id: {$in: vehicleIds}}}}, (err, vehicleData)=>{
//this will be your vehicle details
})
})
}else{
console.log("no Data found");
}
*
The problem with this approach is you have to fire two queries because of the restrictions in Text Search and extra overhead of text index on collections.
Good Thing is you get a lot of ways to perform a search, you can sort results according to relevance with text score, it is fast than the regular expression and you get better results, you get stemming, you get stop words (please ref links that are given).
*
ANSWER 2:
you can use regular expression
db.collection("users").aggregate({$match:{$or:[{name:{$regex: /pink/}}, {email:{$regex: /pink/}}]}},
{$lookup:{from:"vehicles", localField:"vehicleId", foreignFild:"_id", as:"vehicleDetail"}},
{$unwind:{path:"$vehicleDetail"}},
{$match:{$or:[{name:{$regex:/pink/}},{color:{$regex:/pink/}}]}}
(err, data)=>{
})
ANSWER 3:
If you dont want to prefer above options, you can fire normal query too
db.collection("users").aggregate({$match:{$or:[{name:{$regex: /pink/}}, {email:{$regex: /pink/}}]}},
{$lookup:{from:"vehicles", localField:"vehicleId", foreignFild:"_id", as:"vehicleDetail"}},
{$unwind:{path:"$vehicleDetail"}},
{$match:{$or:[{name:{$regex:/pink/}},{color:{$regex:/pink/}}]}}
(err, data)=>{
})
Hope this helps. :-)
Correct me if I am wrong anywhere. :-)
Below steps you should follow:
Find user by name and email.
Populate vehicle
Again match with the color.
db.getCollection('User').aggregate([
{ $match: { email: "h#gmail.com", name: "Hardik"}},
{ $lookup: {from: 'Vehicle', localField: 'vehicleId', foreignField: '_id', as: 'vehicle'} },
{ $match: { "vehicle.color": {$regex: '.*'+ "Pink" +'.*', $options: 'si'}}},
])
Data:
User
{
"_id" : ObjectId("5d51bd5ef5fc3d6486b40ffb"),
"email" : "h#gmail.com",
"name" : "Hardik",
"vehicleId" : ObjectId("5d539786f5fc3d6486b4252b")
}
Vehicle
{
"_id" : ObjectId("5d539786f5fc3d6486b4252b"),
"name" : "Bike",
"color" : "Pink"
}
Output:
{
"_id" : ObjectId("5d51bd5ef5fc3d6486b40ffb"),
"email" : "h#gmail.com",
"name" : "Hardik",
"vehicleId" : ObjectId("5d539786f5fc3d6486b4252b"),
"vehicle" : [
{
"_id" : ObjectId("5d539786f5fc3d6486b4252b"),
"name" : "Bike",
"color" : "Pink"
}
]
}
Hope this helps!

check substring in mongo

I have filter with mongo aggregation:
query['$or'] =
{
firstName: {
$search: filter.keyword,
},
},
{
lastName: {
$regex: filter.keyword, $options: 'xi',
},
},
And later I attach the query to the aggregation.
Have user: firstName: 'john'. lastName: 'snow'.
If filter.keyword = 'jo', 'john', 'john ', all work, return 1 result, but if filter.keyword = 'john s', return 0.
I understand that I need to check not with regex but on the occurrence of the field value in the resulting string, then all subsequent characters will be ignored and the result will be returned to me. I can’t split, and check for an entry in the array, it will give an error for complex names.
This is probably a very stupid question, but I can’t verify the occurrence of the name John in the string 'John S'.
Later query will be attached to aggregation in $match.
Thanks for your answers!!!
You can continue to use regex and break word :
filter.keyword.split(" ").join("|")
This will search for firstname matching "john" or "s" :
$regex: "john|s"
Otherwise you can directly use $text : https://docs.mongodb.com/manual/reference/operator/query/text/
But it will apply a $or on each word, as the solution i propose
And finally you can use $indexOfBytes (substring search):
db.a.insert({name: "john", firstname: "snow"})
db.a.aggregate([{$project: {byteLocation:{$indexOfBytes:["john s", "$name"]}}}])
// { "_id" : ObjectId("5cadb07ddf1f1fe1647e252f"), byteLocation: 0}
db.a.aggregate([{$project: {byteLocation:{$indexOfBytes:["f john s", "$name"]}}}])
// { "_id" : ObjectId("5cadb07ddf1f1fe1647e252f"), byteLocation: 2}

mongoDB, finding a field that is NOT of a specific type

I have some legacy data that stored a string instead of an Object Id, I would like a way to FIND these records...
so as an example -
user : {
_id: ObjectId('234wer234wer234wer')
books_read: [
{
title: "Best book ever",
_id: "123qwe234wer345ert456rty"
},
{
title: "Worst book ever",
_id: "223qwe234wer345ert456rty"
},
{
title: "A Tail of Two Cities",
_id: ObjectId("323qwe234wer345ert456rty")
}
]
}
In this scenario, you can see 2 of the 3 books have _id of strings NOT ObjectIds... I want to update these to ObjectIds - but first I have to find them.
any suggestions greatly appreciated.

mongoose updating a specific field in a nested document at a 3rd level

mongoose scheme:
var restsSchema = new Schema({
name: String,
menu: mongoose.Schema.Types.Mixed
});
simplfied document:
{
name: "Dominos Pizza",
menu:{
"1":{
id: 1,
name: "Plain Pizza",
soldCounter: 0
},
"2":{
id: 2,
name: "Pizza with vegetables",
soldCounter: 0
}
}
}
I'm trying to update the soldCounter when given a single/array of "menu items" (such as "1" or "2" objects in the above document) as followed:
function(course, rest){
rest.markModified("menu.1");
db.model('rests').update({_id: rest._id},{$inc: {"menu.1.soldCounter":1}});
}
once this will work i obviously will want to make it more generic, something like: (this syntax is not working but demonstrate my needs)
function(course, rest){
rest.markModified("menu." + course.id);
db.model('rests').update({_id: rest._id},{$inc:{"menu.+"course.id"+.soldCounter":1}});
}
any one can help with this one?
I looked for an answer but couldn't find nothing regarding the 3rd level.
UPDATE:
Added id to the ducument's subDocument
I think you want add all ids into sub-document, one way you can do as following.
Rest.find({_id: rest._id}, function(err, o) {
// add all ids into sub-document...
Object.keys(o.menu).forEach(function(key) {
o.menu[key].id = key;
});
o.save(function(err){ ... });
});
It seems you want to operate the key in query, I am afraid you cannot do it in this way.
Please refer to the following questions.
Mongodb - regex match of keys for subobjects
MongoDB Query Help - query on values of any key in a sub-object

Text search in Mongo

I have some mongoose schema:
const Article = new Schema({
name: { type: String, required: true },
message: { type: String, default: "" },
searchField: { type: String, default: "" },
...
});
searchField is a combination of name and message fields.
And index:
Article.index({searchField: 'text'});
Then when I need search in list of Articles:
query.$text = { $search: data.search }.
And it works, but with some problems.
The first one - it can't find Article when data.search.length <= 3. For example, name = 'bag' and data.search = 'bag', it doesn't work (ok if language is english).
And the second one - if name = 'subtitles' and data.search = 'sub', there is no result.
What is wrong?
MongoDB shell version: 3.0.6
You can search with a regex. Change your query to this:
Article.find({searchField: /sub/i});
The double / allows you to search only for a part of a word. The i at the end ignores lower- and uppercase.
(Please note that this only searches in the specific field searchField)

Resources