Home > Software engineering >  MongoDB - Search for multiple strings with minimum required hits
MongoDB - Search for multiple strings with minimum required hits

Time:01-02

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)
  • Related