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.
Related
Let say I have a user like bellow
[
{
username : "madhusudhanarao.ch"
}
]
I create index text for username field, but I can't use text search to find this user
db.collection.find({$text : {$search : "madhus" }})
I have to search the full text like madhusudhanarao or ch
I know I can use regex to search but all my code are use $text $search. I can't fix all of them.
As we normally use in sql, the "%like", We use regex for the same purpose on MongoDB
Better read documentation, and you can find Option which you can implement on your search.
db.colection.find({username: {$regex : text }})
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)
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);
I have thousands of items in my gameItem collection with the following as example.
{
"weapon name 1":{price:01},
"weapon name 2":{price:100}
}
and I run a query like so in shell
db.gameItems.find({"weapon name 1":{$exists:1}})
nothing returns but it works when my keys don't have spaces...
First questions: Is there a way to search key values with spaces?
Second question: Should I change my data model to be like so?:
{
weapon:{name: "weapon name 1", price:01}
weapon:{name: "weapon name 2", price:100}
}
And then add an index to the weapon name field? Does this increase search performance with only 2 fields in each weapon document?
Thanks
Edit:
Apologies, it appears the db collection gameItems actually looks like this...
{
last_updated:"2016-04-01",
items:{
"weapon name 1":{price:01},
"weapon name 2":{price:100}
}
}
so how would I search for them inside the item document?
use single quotations where key have space.
run below .
db.gameItems.find({'weapon name 1':{$exists:1}})
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)