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
делает.
базовый Заказ я хотел бы реализовать что-то вроде:
- первые слова, начинающиеся с поиска
- вторые слова, начинающиеся с поискового термина
- слова, где термин не находится в начале слов
- все, как правило, алфавитный, если не более актуальны
Итак, мой вопрос в том, как можно получить разумно отсортированный список предложений для пользователя с поиском 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
это именно то, что требуется!