I am trying to get a number of documents depending on what query you type in.
For example I have a bunch of documents:
[
{name: 'jon', city: 'la', country: 'us'},
{name: 'foo', city: 'ca', country: 'us'},
{name: 'alf', city: 'ca', country: 'us'},
{name: 'olla', city: 'la', country: 'us'},
{name: 'prol', city: 'la', country: 'us'},
{name: 'jon', city: 'ca', country: 'us'}
]
And if I query jon la
I want {name: 'jon', city: 'la'}
as a result.
I use Mongoose and the find()
method.
I've tried both with regex and the text search index.
Using text:
.find({
'$text': {
$search: 'jon la'
},
})
Using regex:
.find({
'$or': [
{ name: { $regex: '^jon|^la' } },
{ city: { $regex: '^jon|^la' } },
{ country: { $regex: '^jon|^la' } }
],
})
I get these results from both approaches:
{name: 'jon', city: 'la', country: 'us'},
{name: 'olla', city: 'la', country: 'us'},
{name: 'prol', city: 'la', country: 'us'},
{name: 'jon', city: 'ca', country: 'us'}
The problem here is I never know if it should find on name or city or any other key in the object. For example the query can be jon us
then I want to get all jon in us and not all jon and all us.
How can I achieve this?
I am thinking about a option to for example
CodePudding user response:
If you explicitly want documents where name=jon
and city=la
(or the regex of those) you just don't use a $or
.find({
name: { $regex: '^jon' },
city: { $regex: '^la' }
})
If the problem is you don't know what is being searched for (e.g., you need to find results where >2 words are matched) things are more complicated, you'd need to use an aggregation pipeline I think.
If what you care about is finding the "best n" matches you can use the full text search scoring
.find(
'$text': {
$search: 'jon la'
},
{ score: { $meta: "textScore" } }
).sort( { score: { $meta: "textScore" } } )
More info on textScore is here: https://www.mongodb.com/docs/manual/reference/operator/query/text/
CodePudding user response:
I solved it by nesting $and
with $or
's with a split.
Live(almost) example:
// Get search query and split by space
const query = 'jon la'.split(' ')
// Choose which fields to be searched for
const searchFields = ['customer.contact.name', 'city', 'age']
// Data for find() method
const data = {}
// Map through search words and wrap in '$or'
data['$and'] = query.map((q: string) => {
return { $or: searchFields.map((v) => ({ [v]: { $regex: q, $options: 'i' } })) };
});
await Model.find(data)