Как я могу манипулировать релевантностью полнотекстового поиска MySQL, чтобы сделать одно поле более «ценным», чем другое? - PullRequest
37 голосов
/ 13 февраля 2009

Предположим, у меня есть два столбца, ключевые слова и контент. У меня есть полнотекстовый индекс по обоим. Я хочу, чтобы строка с foo в ключевых словах была более релевантной, чем строка с foo в содержании. Что мне нужно сделать, чтобы MySQL весил совпадения в ключевых словах выше, чем в содержании?

Я использую синтаксис "сопоставить с".

РЕШЕНИЕ:

смог сделать эту работу следующим образом:

SELECT *, 
CASE when Keywords like '%watermelon%' then 1 else 0 END as keywordmatch, 
CASE when Content like '%watermelon%' then 1 else 0 END as contentmatch,
MATCH (Title, Keywords, Content) AGAINST ('watermelon') AS relevance 
FROM about_data  
WHERE MATCH(Title, Keywords, Content) AGAINST ('watermelon' IN BOOLEAN MODE) 
HAVING relevance > 0  
ORDER by keywordmatch desc, contentmatch desc, relevance desc 

Ответы [ 9 ]

79 голосов
/ 02 марта 2009

Создание трех полнотекстовых индексов

  • а) один в столбце ключевых слов
  • б) один в столбце содержимого
  • в) по одному на ключевое слово и на столбец содержания

Тогда ваш запрос:

SELECT id, keyword, content,
  MATCH (keyword) AGAINST ('watermelon') AS rel1,
  MATCH (content) AGAINST ('watermelon') AS rel2
FROM table
WHERE MATCH (keyword,content) AGAINST ('watermelon')
ORDER BY (rel1*1.5)+(rel2) DESC

Дело в том, что rel1 дает вам релевантность вашего запроса только в столбце keyword (поскольку вы создали индекс только для этого столбца). rel2 делает то же самое, но для столбца content. Теперь вы можете сложить эти две оценки релевантности, применяя любой понравившийся вам вес.

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

Индекс на (ключевое слово, содержание) контролирует ваш отзыв. Ака, что возвращается.

Два отдельных индекса (один только для ключевого слова, другой только для контента) контролируют вашу релевантность. И вы можете применить свои собственные критерии взвешивания здесь.

Обратите внимание, что вы можете использовать любое количество различных индексов (или изменять индексы и веса, которые вы используете во время запроса, основываясь на других факторах, возможно ... искать только по ключевым словам, если запрос содержит стоп-слово ... уменьшить смещение взвешивания для ключевых слов, если запрос содержит более 3 слов ... и т. д.)

Каждый индекс использует дисковое пространство, поэтому больше индексов, больше дисков. И, в свою очередь, более высокий объем памяти для MySQL. Кроме того, вставки займут больше времени, поскольку у вас будет больше индексов для обновления.

Вы должны оценить производительность (будьте осторожны, чтобы отключить кэш запросов MySQL для сравнения, иначе ваши результаты будут искажены) для вашей ситуации. Это не очень эффективно для Google, но это довольно просто и "из коробки", и это почти наверняка намного лучше, чем использование "лайка" в запросах.

Я считаю, что это работает очень хорошо.

20 голосов
/ 14 февраля 2009

На самом деле, использование оператора case для создания пары флагов может быть лучшим решением:

select 
...
, case when keyword like '%' + @input + '%' then 1 else 0 end as keywordmatch
, case when content like '%' + @input + '%' then 1 else 0 end as contentmatch
-- or whatever check you use for the matching
from 
   ... 
   and here the rest of your usual matching query
   ... 
order by keywordmatch desc, contentmatch desc

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

5 голосов
/ 08 июля 2017

Более простая версия, использующая только 2 полнотекстовых индекса (кредиты взяты из @mintywalker):

SELECT id, 
   MATCH (`content_ft`) AGAINST ('keyword*' IN BOOLEAN MODE) AS relevance1,  
   MATCH (`title_ft`) AGAINST ('keyword*' IN BOOLEAN MODE) AS relevance2
FROM search_table
HAVING (relevance1 + relevance2) > 0
ORDER BY (relevance1 * 1.5) + (relevance2) DESC
LIMIT 0, 1000;

Будет выполнен поиск по обоим полным индексированным столбцам по keyword и выбрана соответствующая релевантность в двух отдельных столбцах. Мы исключим элементы, которые не соответствуют друг другу (релевантность1 и релевантность2 равны нулю), и переупорядочим результаты, увеличив вес столбца content_ft. Нам не нужен составной полнотекстовый индекс.

0 голосов
/ 01 февраля 2011

Мне нужно было что-то похожее, и я использовал решение OP, но я заметил, что полный текст не соответствует частичным словам. Поэтому, если слово «арбуз» присутствует в словах или содержании как часть слова (например, watermelonsalesmanager), оно не соответствует и не включается в результаты из-за совпадения WHERE. Поэтому я немного дурачился и подправил запрос ОП к этому:

SELECT *, 
CASE WHEN Keywords LIKE '%watermelon%' THEN 1 ELSE 0 END AS keywordmatch, 
CASE WHEN Content LIKE '%watermelon%' THEN 1 ELSE 0 END AS contentmatch,
MATCH (Title, Keywords, Content) AGAINST ('watermelon') AS relevance 
FROM about_data  
WHERE (Keywords LIKE '%watermelon%' OR 
  Title LIKE '%watermelon%' OR 
  MATCH(Title, Keywords, Content) AGAINST ('watermelon' IN BOOLEAN MODE)) 
HAVING (keywordmatch > 0 OR contentmatch > 0 OR relevance > 0)  
ORDER BY keywordmatch DESC, contentmatch DESC, relevance DESC

Надеюсь, это поможет.

0 голосов
/ 10 августа 2009

В логическом режиме MySQL поддерживает операторы «>» и «<» для изменения вклада слова в значение релевантности, назначенное строке. </p>

Интересно, сработает ли что-то подобное?

SELECT *, 
MATCH (Keywords) AGAINST ('>watermelon' IN BOOLEAN MODE) AS relStrong, 
MATCH (Title,Keywords,Content) AGAINST ('<watermelon' IN BOOLEAN MODE) AS relWeak 
FROM about_data  
WHERE MATCH(Title, Keywords, Content) AGAINST ('watermelon' IN BOOLEAN MODE) 
ORDER by (relStrong+relWeak) desc
0 голосов
/ 17 февраля 2009

Я сделал это несколько лет назад, но без полнотекстового индекса. У меня нет удобного кода (бывший работодатель), но я хорошо помню технику.

В двух словах, я выбрал «вес» из каждого столбца. Например:

select table.id, keyword_relevance + content_relevance as relevance from table
   left join
      (select id, 1 as keyword_relevance from table_name where keyword match) a
   on table.id = a.id
   left join
      (select id, 0.75 as content_relevance from table_name where content match) b
   on table.id = b.id

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

Надеюсь, это поможет!

J.Js

0 голосов
/ 16 февраля 2009

Ну, это зависит от того, что вы конкретно имеете в виду:

Я хочу строку с foo в ключевых словах иметь больше актуальности, чем ряд с foo в содержании.

Если вы имеете в виду, что строка с foo в ключевых словах должна стоять перед любой строкой с foo в содержимом, то я сделаю два отдельных запроса, один для ключевых слов и затем (возможно, лениво, только если это будет запрошено) другой контент.

0 голосов
/ 13 февраля 2009

Если метрика состоит только в том, что все совпадения ключевых слов более «ценны», чем все совпадения контента, тогда вы можете просто использовать объединение с количеством строк. Что-то в этом роде.

select *
from (
   select row_number() over(order by blahblah) as row, t.*
   from thetable t
   where keyword match

   union

   select row_number() over(order by blahblah) + @@rowcount + 1 as row, t.*
   from thetable t
   where content match
)
order by row

Для чего-то более сложного, чем то, где вы хотите применить фактический вес к каждой строке, я не знаю, как помочь.

0 голосов
/ 13 февраля 2009

Насколько я знаю, это не поддерживается полнотекстовым поиском MySQL, но вы можете добиться эффекта, несколько раз повторив это слово в поле ключевого слова. Вместо того, чтобы иметь ключевые слова "foo bar", иметь "foo bar foo bar foo bar", таким образом, foo и bar одинаково важны в столбце ключевых слов, и так как они появляются несколько раз, они становятся более релевантными для mysql.

Мы используем это на нашем сайте, и оно работает.

...