search mongoDB by 2 fields - node.js

I have been looking at some other answers on stack overflow and got as far as I could with that. I learnt that I need to create a text index where I define my Schema which I did like this:
productSchema.index({'title': 'text', 'address.city': 'text'});
If I search by just 1 field, ie: title then I get results as I expect.
Product.find( { $text: { $search: searchTerm } } )
But there is something wrong with my query when trying to search by title and city together.
Product.find( { $text: { $search: searchTerm }, $text: { $search: city } } )
I see no error but I get no results even though I know there should be results for my query. I am not sure if it is because address is an object (according to what I see in Compass)
This is how I defined it in my schema using Mongoose
address: {
city: {type: String, required: true }
},
If I do this:
Product.find().and([{ title: searchTerm }, { 'address.city': city }])
it almost works. But I have to type in the exact title of the product. If the product is called "a rubber duck" and I type in "duck" I get no results. If I type in "a rubber duck" and select the city it is listed in I get back a result.
I have just also tried this:
Product.find( { $and: [ {$text: { $search: searchTerm }}, { address: {city : city } } ] } )
Which seems to work but could probably be improved upon!

Have you looked into the link below? https://docs.mongodb.com/manual/reference/operator/query/or/
There you can find how to add multiple expressions in same query.
If you want to search for titles where title might be equal to A or to B use a query like this: db.inventory.find( { $or: [ { title: "A" }, { title: "B"} ] } ).
Edit If you need data from db to match both expressions in query then use something like this:
Product.find( { $and: [{ address:{city: "CityName"} }, { address: {country : "UK" } } ] } )

Related

Custom search using Mongodb

I have a MongoDB as my database and the backend is written in node.js. I am trying to implement a search for a table which returns me all results with the string entered AND string matching.
For example searching "foo" will return (In that order)
foo maker moo
doo foo doo //The order of word search does not matter as long as it puts the word search first
foobar
fooboo
Currently I have this but I am convinced there is a better way to do it without searching the db twice:
async function(req, res) {
var customerName = req.params.customerName;
//word match
var customers1 = await Models.DummyContactTable.find({
customerName: {
$regex: "^" + customerName,
$options: 'i'
},
IsActive: true
});
//String match
var customers2 = await Models.DummyContactTable.find({
$and: [
{
customerName: {
$regex: customerName, $options: 'i'
}
},
{
customerName: {
$not: {
$regex: "^" + customerName,
}
},
IsActive: true
}
]
});
//Since sometimes we get duplicates, doing a filter and find to de-dup
var customers = customers1.concat(customers2.filter((customer) => !customers1.find(f => f.uuid === customer.uuid)));
If you were using Atlas Search, you could write a query like this:
{
$search: {
autocomplete: {
path: "customerName",
query: "foo"
}}}
// atlas search index definition
{
"mappings": {
"fields": {
"customerName" : {
"type" : "autocomplete"
}}}
If you needed to control the result scores, you could use compound
{
$search: {
compound: {
should: [
{autocomplete: {path: "customerName", query: "foo" }},
{text: {path: "customerName", query: "foo" , score: { boost: { "value" : 3" }}}}
]}}}
In this case, we're using the text operator to split on word boundaries using the lucene.standard analyzer, and boosting those results above. Results from Atlas Search are automatically sorted by score with top results first. Queries are optimized for performance and this query would be done in one pass.
There are a lot of other knobs in the docs to turn depending on your sorting and querying needs (such as using different analyzers, prefix searches, phrase searches, regex, etc).
If you want those kinds of ordering rules I would load up all of your customer names into an application that does the search and perform search & sort entirely in the application. I don't expect even Atlas search to provide this kind of flexibility.
(I don't think the queries you provided achieve the ordering you want either.)

transform raw query to mongodb query the efficient way

In a nodejs app with mongodb storage, I have the following query from user:
const rawQuery = [
'{"field":"ingredient","type":"AND","value":"green and blue"}',
'{"field":"ingredient","type":"AND","value":"black"}',
'{"field":"ingredient","type":"OR","value":"pink"}',
'{"field":"ingredient","type":"OR","value":"orange"}',
'{"field":"place","type":"AND","value":"london"}',
'{"field":"school","type":"NOT","value":"fifth"}',
'{"field":"food","type":"OR","value":"burger"}',
'{"field":"food","type":"OR","value":"pizza"}',
'{"field":"ownerFirstName","type":"AND","value":"Jimmy"}'
];
I have a collection called restaurant, and a collection called owners.
Would this query aim to handle such a search scenario?
const query = {
$and: : [
{ ingredient: 'green and blue' },
{ ingredient: 'black' },
{ $or : [
{ ingredient: 'pink' },
{ ingredient: 'orange' },
]
},
{ place: 'london' },,
{ school: { $ne: 'fifth' } },
{ $or : [
{ food: 'burger' },
{ food: 'pizza' },
]
}
]
};
How can I transform the rawQuery into this mongo query? (Given that it has to be dynamic, because I have many fields, and in this example I just included a couple of them.)
This example query aims to get the restaurants that match the description/place/school/food queries in the restaurant and also to match the owner's first name from another collection. Each restaurant document will have a ownerUuid field that points to the owner in the other collection.
What is the best solution to do a search in the mongodb for such a query in production env?
How can this be achieved with Elasticsearch?

Mongoose full text search not filtering correctly

So basically i have model with a bunch of string fields like so:
const Schema: Schema = new Schema(
{
title: {
type: String,
trim: true
},
description: {
type: String,
trim: true
},
...
}
);
Schema.index({ '$**': 'text' });
export default mongoose.model('Watch', Schema);
where I index all of them.
Now when I search being that this schema is used as a ref for another model I do a search like this where user is an instance of the other model
const { search, limit = 5 } = req.query;
const query = search && { match: { $text: { $search: new RegExp(search, 'i') } } };
const { schemaRes } = await user
.populate({
path: 'schema',
...query,
options: {
limit
}
})
.execPopulate();
and the searching itself seems to work ok, the problem is when search fields starts to be more specific it seems to me the it does not regard it well.
Example
db
{ title: 'Rolex', name: 'Submariner', description: 'Nice' }
{ title: 'Rolex', name: 'Air-King', description: 'Nice' }
When the search param is Rolex I get both items which is ok but when the search param becomes Rolex Air-King i keep on getting both items which to me is not ok because I would rather get only one.
Is there something I could do to achieve this?
Returning both items is correct, since both items match your search params, but with different similarity score.
You can output the similarity score to help sorting the result.
user.aggregate([
{ $match: { $text: { $search: "Rolex Air-King" } } },
{ $set: { score: { $meta: "textScore" } } }
])
// new RegExp("Rolex Air-King", 'i') is not necessary and even invalid,
// as $search accepts string and is already case-insensitive by default
The query will return
[{
"_id": "...",
"title": "Rolex",
"name": "Air-King",
"description": "Nice",
"score": 2.6
},
{
"_id": "....",
"title": "Rolex",
"name": "Submariner",
"description": "Nice",
"score": 1.1
}]
Since the second result item matches your search query (even partially), MongoDB returns it.
You could use the score to help sort the items. But determining the right threshold to filter the result is complex, as the score depends on the word count as well.
On a side note: You can assign different weights to the fields if they are not equally important
https://docs.mongodb.com/manual/tutorial/control-results-of-text-search/

mongodb returning wrong results

Inside aggregation, I want to search two fields if they contain (one or the other) a same string variable. But the search is done only on one field.
This is my code:
const textFilter = req.body.freeTextFilter !== '' ? `.*${req.body.freeTextFilter}*.` : '.';
...
{
$match: {
$or: [
{ name: { $regex: textFilter, $options: 'i' } },
{ comment: { $regex: textFilter, $options: 'i' } }
]
}
},
...
What am I doing wrong? The search is done only on the comment field.
The schema looks like this (there are more field):
const Activity = new Schema({
name: {
type: String,
required: true
},
comment: {
type: String
},
},
If one document has the field 'name' that contains a specific text, and another one has the field 'comment' that contains the same specific text, i expect that the query return both documents.
If i search only on the 'name' field (without $or), it doesn't returns anything.
Ok, problem solved. The query works fine, the problem was somewhere else. As I wrote before, I didn't get the right results, but it was because of the paging logic that was wrong.

Doing partial search with mongoose

I'm trying to get Mongoose to return results in a query when I only give a partial query. For example: I have a 'Company' schema that lists a bunch of companies. A document example:
{
"_id" : ObjectId("57aabeb80057405968de1539"),
"companyName" : "Vandelay Industries",
"owner" : "Ary Vandelay",
"inception" : 2012,
"__v" : 1
}
So if I do a search query like this:
Company.findOne(
{ companyName: Vandelay Industries }, function (err, company) {
if (company) {
//do stuff
}
});
This will produce the document. But If I do the following, I won't get a result:
Company.findOne(
{ companyName: Vandelay }, function (err, company) {
if (company) {
//do stuff
}
});
I would like to be able to do these sorts of partial searches and still get a result. Is there any way to do that with Mongoose?
In order to achieve this you can use a regex search to get the required result.
var searchKey = new RegExp('Vandelay', 'i')
Company.findOne({ companyName: searchKey }, function (err, company) {
if (company) {
//do stuff
}
});
Refer this stackoverflow post.
You can use this query to get result on specific value
Company.findOne({"companyName": /Vandelay/},function(err,company){
if(!err){
console.log(company);
}
});
To get result faster you should use indexing ref https://docs.mongodb.com/manual/text-search/.
db.Company.createIndex( { companyName: "text" } )
then you can search
db.Company.find( { $text: { $search: "company name" } } )
But this only support full word search not partial, so adding an extra line to this will help
db.Company.find({ $or: [
{ $text: { $search: query } },
{ companyName: { $regex: '^' + 'copmany name'} }
]}
This will help you search the results faster than normal
Are you doing a fulltext search? If you do that:
TagGroup.find({
$text: {
$search: text
}
}, {
score: {
$meta: "textScore"
}
}).sort({
score: {
$meta: 'textScore'
}
})
Try that code below. Also you need create a index on that schema
TagGroupSchema.index({
"$**": "text"
});
Here is the document
You can use elasticsearch to do that either, when the documents grows, you should consider this way.

Resources