MongoDB text search: find both words in one document [duplicate] - mongodb

I have an index on an array "keys" that I am using to provide full text functionality to my applicaiton.
With the release of 2.4.3, I'd like to utilize the "text" index type. I insured a "text" index type on my array "keys" and it seems to work SUPER fast (faster than my old keywords full text method).
The problem is, my app assumes that fields are inclusive (AND). By default, the text search ORs my parameters.
Does anyone know of a way to run a text search inclusively?
For example:
db.supplies.runCommand("text", {search:"printer ink"})
should return results with both printer and ink, instead of all results with either printer or ink.

Give a try to:
db.supplies.runCommand("text", {search:"\"printer\" \"ink\""})
Also, here's a quote from docs:
If the search string includes phrases, the search performs an AND with
any other terms in the search string; e.g. search for ""twinkle
twinkle" little star" searches for "twinkle twinkle" and ("little" or
"star").

You can wrap each word in double-quotes:
let keywords = ctx.params.query.split(/\s+/).map(kw => `"${kw}"`).join(' ');
match.$text = { $search: keywords, $caseSensitive: false };
There is a downside if the user inputs a quoted string this will not work. You'd have to parse out quoted strings first.

As #alecxe pointed out earlier, to do AND search on text index column you need to double quote each search word.
Below is a quick one-liner for your requirement.
db.supplies.runCommand("text", {search: "printer ink".split(" ").map(str => "\""+str+"\"").join(' ')})

Here is a simple function I made to search using subwords in node. Hope it helps someone
Let's suppose a user search for pri nks so it should satisfy printer and inks but $text search doesn't allow for this so here is my simple function:
var makeTextFilter = (text) => {
var wordSplited = text.split(/\s+/);
/** Regex generation for words */
var regToMatch = new RegExp(wordSplited.join("|"), 'gi');
let filter = [];
searchFieldArray.map((item,i) => {
filter.push({});
filter[i][item] = {
$regex: regToMatch,
$options: 'i'
}
})
return filter;
}
and use it in your query like this
let query = {...query, $or: makeTextFilter(textInputFromUser)}
tableName.find(query, function (err, cargo_list)

Related

MongoDB and NextJS: Find a certain data matches regardless if uppercase or lowercase

The goal of this code is to display the current numbers of death, recoveries and critical for covid 19 around the world.
The search function codes are as follows:
const search = (e) => {
e.preventDefault() //to avoid page redirection
const countryMatch = countryCollection.find(country => country.country_name === targetCountry)
if (!countryMatch || countryMatch === null|| countryMatch === 'undefined') {
alert("Country Does Not Exist, use another name.")
setName("")
setTargetCountry("")
} else {
setName(countryMatch.country_name)
setDeathCount(toNum(countryMatch.deaths))
setCriticalCount(toNum(countryMatch.serious_critical))
setRecoveryCount(toNum(countryMatch.total_recovered))
}
}
Our task is to find a country regardless if its in upper or lower case. Eg: Malaysia vs malaysia.
REGULAR EXPRESSION
What you need is regular expression or RegExp. MongoDb supports regular expression for your searches.
In Your case it can be something like
countryCollections.find({'country':new RegExp(countryName,flag)},callback)
flag determines how you want to search
for case insensitive search use 'i'
More about RegExp can be found on mongoDB docs https://docs.mongodb.com/manual/reference/operator/query/regex/
According to your usage of MongoDB, I would say, that this case is an excellent case to using text indexes.
Here is an example for you:
Schema.index(
// making field available for $text search and $meta sorting
{
'field': 'text',
'embedDoc.field': 'text',
},
{
//options of index
weights: // weight for each field
{
'field': 2,
'embedDoc.field': 1,
},
name: 'Countries', // Index Name for Mongo Compass and .explain debug
})
I guess you should try that. It will solve all your potential problems with text search. Like ' or diacritic symbols in searching, lower-uppercase and so on. But please, check the documentation of text indexes, before implementing them, it's quite sensitive and flexible for any cases. But there is no universal silver bullet.

mongoose Look for an exact word in a phrase

As of now when I search for a text, it is searching through each character using this code:
model.find( { title: { $regex: /word/i } } )
but the result comes like 'word', 'word123' or '333word3' and I want only the titles that contain 'word' word in it. eg: 'this is a word'
Simply put \b allows you to perform a “whole words only” search using a regular expression in the form of \bword\b
Model.find({
title: {
$regex: "\\bword\\b"
}
})
//or
Model.find({
title: /\bword\b/
})
If you want ignore such cases: foo-word, bar:word, word-buz use this:
Model.find({
title: {
$regex: "\\b(^|\\s|[^\\W])word(\\s|[^\\W]|$)\\b"
}
})
You're regex expression dosen't exclude those options you said
You need to either:
Change up the regex to something like / word /i if you want to force a space around the word, mind you if combinations like word-word2 are relevant you'll have to account for those as well in the regex you form.
Use Mongo's $text option, for that you'll have to build a text index on that field. Keep in mind that Mongo stems words when it indexes the text field meaning a word like cars would become car meaning this might not be the best option for you if an exact match is a requirement.

searching in mongo specifically

I have a question, lets say I have a collection called contact :
[
{"firstName": "Adam", "lastName":"Peter", "email":"adam#peter.com"},
{"firstName": "Adam", "lastName":"John", "email":"adam#john.com"},
{"firstName": "Adam", "lastName":"Petkovic", "email":"adam#petkovic.com"}
]
What I want is to search specifically, for example: I want to search "Adam peter" then I want to have a result of the first one ONLY which has Adam and peter.
I use meteor + mongo + react for my application.
Any suggestion / recommendation would be high appreciated.
Thanks for all the answers, but probably I need to ask more specific in order to get more appropriate answer.
Scenarios:
I only have 1 text box to search all the fields.
So:
when I enter "Adam", I expect to have 3 results. but when I enter "Adam Peter" I expect to have 1 result only.
When I enter "peter.com" it should have 1 result
When I enter "John", it should have 1 result
When I enter "Adam Pet" it should have 2 results.
From the answer here, below query should work fine.
db.contacts.find( { firstName: /^Adam$/i, lastName: /^peter$/i });
The query in MongoDB is case sensitive, if you want to query contact by ignoring case, you should use a regular expression, but it may not efficient.
db.contact.findOne({firstName: /^adam$/i, lastName: /^peter$/i})
it will much better if you always save these name value in lowercase, and query in lowercase
db.contact.findOne({firstName: 'adam', lastName: 'peter'})
Assuming that the rules that you are applying are:
If a single word, then that could match any field
two words mean "firstname surname"
In that case, you can't use text indices, but instead need to do some work before the mongo search.
First, split the words on whitespace, and then determine if there are one or two words. If there is one word, check that against all fields. If there are two, then only check the first word against the first name, and the second against the lastname.
// assuming input is in variable call 'term'
var words = term.trim().split(/\s+/) || [];
if(words.length === 0) {
return;
}
var first = new RegExp(words[0], 'i');
if(words.length === 2) {
var second = new RegExp(words[1], 'i');
return Contact.find({firstName: first, lastName: second});
else if(words.length === 1) {
return Contact.find({$or: [ {firstName: first}, {lastName: first}, {email: first}]})
}
Also, depending on how large your collection is, it might be better to wrap this up into a Meteor method, so that the search takes place on the server. Otherwise, you will have to publish the whole collection on the client to be able to do the search. That might be fine for a small collection though.
UPDATE:
Based on your examples, I think your rules are:
1. Search terms are combined with AND operator (e.g. Adam Pet returns two rows, not three).
2. Search terms use regular expression matching (Pet matches even though it's not any of the words).
Rule 2 means that text indices won't work, so you will need to build up a complex regex query document using $and and $or for each item:
// assuming input is in variable call 'term'
var words = term.trim().split(/\s+/) || [];
var query = {
"$and": []
};
words.forEach(function(token) {
var reg = new RegExp(token);
var innerQ = {"$or": [ {firstName: reg}, {lastName: reg}, {email: reg}]};
query["$and"].push(innerQ);
});
return Contact.find(query);

mongo text search using $text is not working

I am trying to do mongo text search using indexing and $text
The model i have is
var authorSchema = new mongoose.Schema(
{
authorId : Number,
Description : String,
firstName : String
});
authorSchema.index({ firstName: 'text'});
i am creating the index on first name
when i do the search as shown below
router.route('/search')
.get(function(req,res){
Authors.find({ $text : { $search : req.params.search }},{"_id":0,"firstName":1},function(err,authors){
res.send(authors);
})
})
search seems not to provide expected result ie,
I have two documents in the collection where the first name is kumar and sam kumar
*When i search for kumar search will get both the documents
*When i search for sam kumar i will again get both the documents which seems to be not right what is expected is search is only sam kumar
What am i doing wrong
Please say how can i accomplish the search when i search for entire first name search should return only single document
It looks like you are trying to find phrase, in your case sam kumar and trying to find a document which contains full phrase.
in this case, wrap your phrase with double quotes (")
e.g.
var phrase = "\"" + req.params.search + "\"";
Authors.find({ $text : { $search : phrase }},{"_id":0,"firstName":1},function(err,authors){
res.send(authors);
this should return what are you expecting.
here is documentation stating how to search phrases.
please see https://docs.mongodb.org/manual/reference/operator/query/text/#phrases
Above snipes assume that you are search for a complete phrase sam kumar. However if you want to match all documents where both words sam and kumar exists anywhere in document, then you'll need to calculate text score and filter documents with score of 1 or more.
Please see https://docs.mongodb.org/manual/tutorial/text-search-in-aggregation/#match-on-text-score for reference.

MongoDB Text Search AND multiple search words

I have an index on an array "keys" that I am using to provide full text functionality to my applicaiton.
With the release of 2.4.3, I'd like to utilize the "text" index type. I insured a "text" index type on my array "keys" and it seems to work SUPER fast (faster than my old keywords full text method).
The problem is, my app assumes that fields are inclusive (AND). By default, the text search ORs my parameters.
Does anyone know of a way to run a text search inclusively?
For example:
db.supplies.runCommand("text", {search:"printer ink"})
should return results with both printer and ink, instead of all results with either printer or ink.
Give a try to:
db.supplies.runCommand("text", {search:"\"printer\" \"ink\""})
Also, here's a quote from docs:
If the search string includes phrases, the search performs an AND with
any other terms in the search string; e.g. search for ""twinkle
twinkle" little star" searches for "twinkle twinkle" and ("little" or
"star").
You can wrap each word in double-quotes:
let keywords = ctx.params.query.split(/\s+/).map(kw => `"${kw}"`).join(' ');
match.$text = { $search: keywords, $caseSensitive: false };
There is a downside if the user inputs a quoted string this will not work. You'd have to parse out quoted strings first.
As #alecxe pointed out earlier, to do AND search on text index column you need to double quote each search word.
Below is a quick one-liner for your requirement.
db.supplies.runCommand("text", {search: "printer ink".split(" ").map(str => "\""+str+"\"").join(' ')})
Here is a simple function I made to search using subwords in node. Hope it helps someone
Let's suppose a user search for pri nks so it should satisfy printer and inks but $text search doesn't allow for this so here is my simple function:
var makeTextFilter = (text) => {
var wordSplited = text.split(/\s+/);
/** Regex generation for words */
var regToMatch = new RegExp(wordSplited.join("|"), 'gi');
let filter = [];
searchFieldArray.map((item,i) => {
filter.push({});
filter[i][item] = {
$regex: regToMatch,
$options: 'i'
}
})
return filter;
}
and use it in your query like this
let query = {...query, $or: makeTextFilter(textInputFromUser)}
tableName.find(query, function (err, cargo_list)