2

There is an application with search input that gives an opportunity to search for contacts by their information stored in database.

For example, I can type 0972133122 Alan and my search engine must return all contacts whose firstname is Alan & whose numbers match 0972133122 string.

Of course, I can just type Alan 0972, for instance, and there must be returned all possible contacts matching this pattern. The query order may be different, so that I can type 0972 Alan Smith, and if there are 2 contacts with Alan names and whose phone numbers start with 0972, then additional Smith clarification should return the only 1 contact.

I suggest built in phone applications for Android make use of this search algorithm: enter image description here
So that my goal is to achieve similar result, but I do know how to do this. Here my code:
GraphQL query

query contacts {
  contacts(input: {
    contactQuery: "Alan Smith"
  }) {
    name {
      firstName
      lastName
    }
  }
}

NodeJS query to MongoDB

const conditions = {};
const expr = contactQuery
  .split(' ')
  .map((contact) => new RegExp(`${contact}`, 'i'))

conditions.$or = [
  { 'firstName': { $in: expr } },
  { 'lastName': { $in: expr } },
  { 'university': { $in: expr } },
  { emails: { $elemMatch: { email: { $in: expr } } } },
  { phones: { $elemMatch: { phone: { $in: expr } } } },
  { socials: { $elemMatch: { id: { $in: expr } } } },
]

const contacts = await this.contacts
  .find(conditions, undefined)
  .exec()

This works partly, but I receive unwanted documents from MongoDB:

{
  contacts: [
    {
      firstName: "Alan",
      lastName: "Smith",
      university: "KNTU",
      ...
    },
    {
      firstName: "Alan",
      lastName: "Alderson", // should not be returned
      university: "ZNU",
      ...
    },
    ...
  ]
}

But I need to get one contact that has strictly Alan firstname and Smith lastname. If it's impossible to do with MongoDB, -- please, provide me an example of SQL query. Any suggestions & solutions will be accepted!

Please, let me know if my question still is not clear.

3
  • in SQL you could do WHERE firstname = 'Alan' AND lastname = 'Smith', it looks like your current query is applying an OR instead of an AND Commented Jul 9, 2021 at 11:33
  • There can be more than one field. I can clarify my query with Alan Smith KNTU, so that if there is another Alan Smith which studies at another university(not ZNTU) I should receive the only 1 contact who studies at KNTU. There can be many fields, and my application cannot know exactly what fields client passes. Commented Jul 9, 2021 at 11:36
  • If I type ZNTU Smith, -- there should be contacts matching this pattern. But my app do not know exactly the order of the fields and doesn't know how many they are. Commented Jul 9, 2021 at 11:37

1 Answer 1

3
+50

Firstly, you need to separate out the numbers and words from the search text and then you can create a possible combination of it for an example:

  1. FirstName: Alan, LastName: Smith
  2. FirstName: Smith, LastName: Alan

Using regex you can do this easily and then you can use logical operators of mongodb to create your query like this

Approach 1

db.collection.find({
  $or: [
    {
      $and: [
        {
          firstName: {
            $regex: "Alan",
            $options: "i"
          }
        },
        {
          lastName: {
            $regex: "Smith",
            $options: "i"
          }
        }
      ]
    },
    {
      $and: [
        {
          firstName: {
            $regex: "Smith",
            $options: "i"
          }
        },
        {
          lastName: {
            $regex: "Alan",
            $options: "i"
          }
        }
      ]
    }
  ]
})

Here is the link to the playground for you to look at it in action Mongo Playground

Approach 2

Another way is where you concat all the searchable keys into one field and then use regex to filter it out like this

db.collection.aggregate([
  {
    $addFields: {
      text: {
        $concat: [
          "$firstName",
          " ",
          "$lastName",
          " ",
          "$university",
          " ",
          "$phones"
        ]
      }
    }
  },
  {
    $match: {
      text: {
        $regex: "(?=.*?(0972))(?=.*?(Alan))(?=.*?(Smith))",
        $options: "i"
      }
    }
  },
  {
    $project: {
      text: 0
    }
  }
])

Code to build the query:

let text = "0972 Alan Smith";
let parts = text.split(" ");
let query = parts.map(part => "(?=.*?("+part+"))").join("");

console.log(query);

But you need to check the performance implication of this approach or you can create a view and then query to view to make your query more cleaner

Here is the link to the playground for you to look at it in action Mongo Playground

Sign up to request clarification or add additional context in comments.

7 Comments

Thank you for your answer! Please, could you provide me with a full automated example? If there're no db I would just join all key's values of each object in array and find what I need using regex. I want to remember that I'm trying to implement search by multiple fields using database(not just plain javascript), and from the client Im getting string query only which can contains 1 or more values(e.g. Alan Smith ZNTU -- or phone number, etc.)
Welcome, @LeonardoDiPierro! I have edited my answer with one more approach. If you still want to go with approach 1 will try to give you an automated example to build a query for that approach
The performance is not priority to me. I think 2nd approach looks pretty but it seems(I cannot check 2nd approach right now, so that correct me, please) it doesn't consider the fact that order can bу different which makes draw up a combination of all possible permutations. So that be confident to use the approach you find easy to implement... Thanks in advance!
Is there way to transform back a result of second example to an object?
In the second approach, you don't need to create all the possible permutations as regex will handle that part. You try to test your use cases in the playground and I have also updated the playground and answer which will return the original document
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.