MySQL-как получить результаты поиска с точной релевантностью

Я много раз возвращался к этой проблеме, и я никогда не находил правильного ответа.

можно ли выполнить поиск MySQL, который возвращает фактические точно отсортированные результаты по релевантности?

Я пытаюсь создать форму поиска ajax, которая делает предложения по мере того, как пользователь вводит в поле ввода, и не нашел достойного решения для этого, используя только чистые запросы MySQL. Я знаю, что есть доступные серверы поиска, такие как ElasticSearch, Я хочу знать, как это сделать только с необработанным запросом MySQL.


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

Запрос ("Bio...")- ПОЛНОТЕКСТОВЫЙ ЛОГИЧЕСКИЙ РЕЖИМ

SELECT name, MATCH(name) AGAINST('bio*' IN BOOLEAN MODE) AS relevance
FROM subjects
WHERE MATCH(name) AGAINST('bio*' IN BOOLEAN MODE)
ORDER BY relevance DESC
LIMIT 10

результаты

name                                        |  relevance
--------------------------------------------------------
Biomechanics, Biomaterials and Prosthetics  |  1
Applied Biology                             |  1
Behavioural Biology                         |  1
Cell Biology                                |  1
Applied Cell Biology                        |  1
Developmental/Reproductive Biology          |  1
Developmental Biology                       |  1
Reproductive Biology                        |  1
Environmental Biology                       |  1
Marine/Freshwater Biology                   |  1

чтобы показать, насколько плохи эти результаты, вот сравнение с простым LIKE запрос, который показывает все более релевантные результаты, которые не были показаны:

Запрос ("Bio...")- Как

SELECT id, name
WHERE name LIKE 'bio%'
ORDER BY name

результаты

name                                        |  relevance
--------------------------------------------------------
Bio-organic Chemistry                       |  1
Biochemical Engineering                     |  1
Biodiversity                                |  1
Bioengineering                              |  1
Biogeography                                |  1
Biological Chemistry                        |  1
Biological Sciences                         |  1
Biology                                     |  1
Biomechanics, Biomaterials and Prosthetics  |  1
Biometry                                    |  1

и уже вы видите, сколько предметов не предложил, хотя это более вероятно, что пользователь будет искать.

проблема с использованием LIKE однако, как искать по нескольким словам и в середине слов, таких как FULLTEXT делает.

базовый Заказ я хотел бы реализовать что-то вроде:

  1. первые слова, начинающиеся с поиска
  2. вторые слова, начинающиеся с поискового термина
  3. слова, где термин не находится в начале слов
  4. все, как правило, алфавитный, если не более актуальны

Итак, мой вопрос в том, как можно получить разумно отсортированный список предложений для пользователя с поиском MySQL через несколько слов?

4 ответов


Вы можете использовать строковые функции, такие как:

select id, name
from subjects
where name like concat('%', @search, '%')
order by 
  name like concat(@search, '%') desc,
  ifnull(nullif(instr(name, concat(' ', @search)), 0), 99999),
  ifnull(nullif(instr(name, @search), 0), 99999),
  name;

Это возвращает вам все записи, содержащие @search. Сначала те, которые имеют его в начале, затем те, которые имеют его после пустого, затем по положению вхождения, затем в алфавитном порядке.

name like concat(@search, '%') desc использует логическую логику MySQL, кстати. 1 = true, 0 = false, поэтому порядок этого нисхождения дает вам true первым.

скрипка SQL:http://sqlfiddle.com#!9 / c6321a/1


для других посадок здесь (как и я): по моему опыту, для достижения наилучших результатов вы можете использовать условное в зависимости от количества слов поиска. Если используется только одно слово, например " %word%", в противном случае используйте логический полнотекстовый поиск, например:

if(sizeof($keywords) > 1){
   $query = "SELECT *,
             MATCH (col1) AGAINST ('+word1* +word2*' IN BOOLEAN MODE) 
             AS relevance1,
             MATCH (col2) AGAINST ('+word1* +word2*' IN BOOLEAN MODE) 
             AS relevance2
             FROM table1 c
             LEFT JOIN table2 p ON p.id = c.id
             WHERE MATCH(col1, col2) 
             AGAINST ('+word1* +word2*' IN BOOLEAN MODE) 
             HAVING (relevance1 + relevance2) > 0
             ORDER BY relevance1 DESC;";
    $execute_query = $this->conn->prepare($query);
}else{          
   $query = "SELECT * FROM table1_description c
             LEFT JOIN table2 p ON p.product_id = c.product_id
             WHERE colum1 LIKE ? AND column2 LIKE ?;";
        // sanitize
        $execute_query = $this->conn->prepare($query);
        $word=htmlspecialchars(strip_tags($keywords[0]));
        $word = "%{$word}%";
        $execute_query->bindParam(1, $word);
        $execute_query->bindParam(2, $word);
    }

Я пробовал это на основе вашего описанного заказа.

SET @src := 'bio';
SELECT name,
name LIKE (CONCAT(@src,'%')),
         LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(name,' ',2),' ',-1),LENGTH(@src)) = @src,
         name LIKE (CONCAT('%',@src,'%'))
FROM subjects
ORDER BY name LIKE (CONCAT(@src,'%')) DESC,
         LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(name,' ',2),' ',-1),LENGTH(@src)) = @src DESC,
         name LIKE (CONCAT('%',@src,'%')) DESC,
         name

http://sqlfiddle.com#!9 / 6bffa/1

Я подумал, может быть, вы даже захотите включить количество случаев @src тоже подсчитать количество вхождений строки в поле VARCHAR?


Это лучшие результаты можно получить, используя комбинацию ответов:

$searchTerm = 'John';
// $searchTerm = 'John Smit';
if (substr_count($searchTerm, ' ') <= 1)
    $sql = "SELECT id, name
    FROM people
    WHERE name like '%{$searchTerm}%')
    ORDER BY
      name LIKE '{$searchTerm}%') DESC,
      ifnull(nullif(instr(name, ' {$searchTerm}'), 0), 99999),
      ifnull(nullif(instr(name, '{$searchTerm}'), 0), 99999),
      name
    LIMIT 10";
}
else {
$searchTerm = '+' . str_replace(' ', ' +', $searchTerm) . '*';
$sql = "SELECT id,name, MATCH(lead.name) AGAINST('{$searchTerm}' IN BOOLEAN MODE) AS SCORE
        FROM lead
    WHERE MATCH(lead.name) AGAINST('{$searchTerm}' IN BOOLEAN MODE)
    ORDER BY `SCORE` DESC
    LIMIT 10";

убедитесь, что вы установили полнотекстовый индекс в столбце (или несколько столбцов, если это то, что вы используете) и сбросьте индексы с помощью OPTIMIZE table_name.

лучше всего об этом, если вы наберете Jo, тогда человек, у которого есть имя Jo будет ранг выше, чем John это именно то, что требуется!