Я неоднократно пересматривал эту проблему, и я никогда не нашел правильного ответа.
Возможно ли выполнить поиск MySQL, который возвращает ACTUAL точно отсортированные результаты по релевантности?
Я пытаюсь создать форму поиска ajax, которая делает предложения, когда пользователь вводит в поле ввода, и не нашел подходящего решения для этого, используя только чистые запросы MySQL. Я знаю, что есть поисковые серверы, такие как ElasticSearch, я хочу знать, как это сделать, только с необработанным MySQL-запросом.
У меня есть таблица школьных предметов. Есть менее 1200 строк, и это никогда не изменится. Давайте проведем базовый поиск FULLTEXT, где пользователь начнет вводить «Bio».
Запрос («Bio …») – FULLTEXT BOOLEAN MODE
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 …») – LIKE
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 по нескольким словам?
Вы можете использовать строковые функции, такие как:
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, поэтому упорядочение этого спуска дает вам истинное первое.
Скрипт SQL: http://sqlfiddle.com/#!9/c6321a/1
Для других, приземляющихся здесь (как и я): по моему опыту, для достижения наилучших результатов вы можете использовать условное в зависимости от количества поисковых слов. Если есть только одно слово, используйте LIKE '% 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?