Поиск MySQL в нескольких столбцах, содержащих подстроку поискового запроса

#mysql #sql #search #full-text-search

#mysql #sql #Поиск #полнотекстовый поиск

Вопрос:

Я создал таблицу базы данных, содержащую клиентов. Пользователи приложения должны иметь возможность выполнять поиск в этой таблице. Определение таблицы выглядит следующим образом:

 CREATE TABLE `Customers` 
(
      `Id` int(11) UNSIGNED NOT NULL,
      `Firstname` varchar(50) DEFAULT NULL,
      `Insertion` varchar(12) DEFAULT NULL,
      `Lastname` varchar(50) NOT NULL,
      `ClientNumber` varchar(30) DEFAULT NULL,
      `DateCreated` int(10) UNSIGNED NOT NULL,
       -- More columns...
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
    
ALTER TABLE `Customers`
    ADD PRIMARY KEY (`Id`);

ALTER TABLE `Customers` 
    ADD FULLTEXT KEY `search_fulltext` (`Firstname`,`Lastname`,`ClientNumber`);
    
ALTER TABLE `Customers`
    MODIFY `Id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT;
 

Желаемый результат:

Когда пользователь выполняет поиск клиентов, приложение ищет совпадения в столбцах Firstname , Lastname , и ClientNumber . При поиске ‘John Doe’ должны быть выбраны все строки, содержащие любую подстроку этого поискового запроса в любом из столбцов Firstname, Lastname, ClientNumber. (Для принципа вопроса столбец ClientNumber можно поместить в фоновый режим).

Мои попытки

Следующий запрос был моей первой попыткой:

 SELECT * 
FROM Customers 
WHERE Firstname LIKE CONCAT('%', :searchText, '%') 
   OR Lastname LIKE CONCAT('%', :searchText, '%') 
   OR ClientNumber LIKE CONCAT('%', :searchText, '%') 
LIMIT 0, 10
-- LIMIT here could be any limit.
 

Когда пользователь ищет строку, которая точно отображается как подстрока в столбце, она работает так, как ожидалось.

Скажем, имя пользователя клиента: Джон Доу ( Firstname = Джон, Lastname = Доу) Когда пользователь вводит поисковый запрос «Джон», или «Джо», или «Джо», он совпадает в Firstname столбце. Если пользователь вводит поисковый запрос ‘Doe’, он совпадает в Lastname столбце. Оба случая возвращают ожидаемые строки.

Проблема возникает, когда текст поиска представляет собой полное имя, например: ‘John Doe’. Несмотря на то, что этот текст поиска содержит возможные совпадения в нескольких столбцах, строки не выбраны.

Я понял, что запрос не подходит для того, чего я хочу достичь, поэтому я попытался создать FULLTEXT индекс для столбцов Firstname Lastname и ClientNumber . Оказывается, это работает только для значений, которые удовлетворяют совпадению в каждом отдельном столбце. Поиск полного имени по-прежнему не работает.

Это полнотекстовый запрос, который я использую:

 SELECT * 
FROM Customers 
WHERE MATCH (Firstname, Lastname, ClientNumber)
    AGAINST('John Doe' IN NATURAL LANGUAGE MODE)
 

Этот запрос тоже не решает мою проблему.

Итак, в более коротком предложении: пользователь должен иметь возможность выполнять поиск по полному имени, в то время как имя разделено на несколько столбцов.

Другой вариант, который я могу придумать сам, — это создать дополнительный столбец в таблице, просто сохранить полное имя в виде одной строки и использовать этот столбец для выполнения поиска.

В будущем мы также хотели бы поддерживать поиск по текстам, которые очень похожи на какое-то значение. Поэтому, когда пользователь ищет «Питер Джонссон», он также выбирает строки, содержащие «Пит Джонсон» (одиночный r / s). Но, возможно, это слишком далеко от контекста вопроса.

Комментарии:

1. Может быть, объединить поля имен и выполнить поиск по этому вычисляемому полю? Может быть, выбрать клиента из выпадающего списка?

2. @June7 Для небольшого количества записей в таблице можно рассмотреть поле со списком. Но когда записей много, было бы непрактично выбирать все из таблицы для заполнения выпадающего списка. Объединение полей в предложении WHERE может быть вариантом, но кажется немного медленным.

3. Почему многие записи должны быть непрактичными? У меня есть выпадающие списки, в которых перечислены тысячи элементов.

Ответ №1:

Прямого ответа на ваш вопрос нет, но есть много решений для вашего варианта использования, вот некоторые из них :

  • Манипулируйте текстом, который вы хотите сопоставить, и создайте более расширенный запрос, например:
 String[] splittedSearchTextBySpace = searchText.splite(" ");

String whereClause = "";
foreach(String word : splittedSearchTextBySpace){
    whereClause =
        whereClause  
        """Firstname LIKE '%:word%' OR
        Lastname LIKE '%:word%' OR
        ClientNumber LIKE '%:word%'"""
          " OR ";
}

whereClause = whereClause.substr(0,-4) // remove last " OR "

cosnt query = "SELECT * FROM Customers WHERE "
      whereClause  
    " LIMIT 0, 10";

callDB(query);

// this is just an example, you could do more than that

 
  • В зависимости от используемого вами языка программирования вы найдете множество библиотек / пакетов, которые предоставляют утилиты поиска и охватывают упомянутые вами варианты использования сообщества и многое другое, например :
    • Java : Lucene …
    • Php : Tntsearch …
  • Вы также можете выйти за рамки библиотек и использовать сервисные / поисковые системы (PAAS или самостоятельно размещенные один раз), которые предоставляют утилиты поиска, к которым вы будете подключаться, как и к вашей службе базы данных, например :
    • elasticSearch
    • Algolia
    • Solr

Бонус:

  • Большинство этих сервисов и библиотек используют структуру данных, называемую инвертированным индексом.
  • Некоторые связанные ключевые слова: стемминг, индексации, n-граммы, нечеткий поиск, полнотекстовый поиск, маркеры, алгоритмы ранжирования, Bm25 ..
  • Чтобы просмотреть оценку, полученную при полнотекстовом поиске mysql, используйте :
 SELECT 
  id, 
  MATCH(Firstname, Lastname, ClientNumber) 
    AGAINST('John Doe' IN NATURAL LANGUAGE MODE) AS score 
FROM demo
 
  • Взгляните на IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION другой режим полнотекстового поиска mysql.