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

Предположим, у меня есть два столбца, ключевые слова и контент. У меня есть полнотекстовый индекс для обоих. Я хочу, чтобы строка с 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 

person Buzz    schedule 13.02.2009    source источник


Ответы (9)


На самом деле, использование оператора 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

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

person notnot    schedule 13.02.2009
comment
Использование оператора like не лучший способ запуска поиска. Во-первых, если вы не разделите строки, вы будете совпадать только в точном порядке. то есть поиск LIKE '%t-shirt red%' не будет соответствовать «Красной футболке» в вашей базе данных. Во-вторых, вы получаете больше времени для выполнения запроса, поскольку LIKE выполняет полное сканирование таблицы. - person ChrisG; 01.04.2012
comment
@ChrisG LIKE выполняет полное сканирование таблицы, когда оно используется в предложении FROM, а не в SELECT - person gontard; 28.07.2014

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

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

Затем ваш запрос:

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, но это довольно просто и «нестандартно», и это почти наверняка намного лучше, чем использование вами «нравится» в запросах.

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

person mintywalker    schedule 02.03.2009
comment
Работает хорошо и имеет смысл. Спасибо! - person Bretticus; 31.08.2010
comment
Кажется, я не мог заставить это работать (возможно, потому, что я не добавил третий индекс), но изменение условия where на: rel1 > 0 ИЛИ rel2 > 0 решило мою проблему, так что спасибо. - person Ultimate Gobblement; 25.10.2011
comment
@mintywalker должен ли Order By не быть ORDER BY (rel1*1.5)+(rel2) DESC, чтобы получить наивысший балл и, следовательно, быть более актуальным в первую очередь? - person PanPipes; 23.06.2016
comment
@PanPipes да, это должно быть DESC, так как более высокая релевантность лучше подходит - person Flame; 15.11.2016
comment
@mintywalker Я просто хотел сказать спасибо, этот точный запрос (адаптированный к нашей схеме) пыхтел как минимум пять лет на веб-сайте сообщества с десятками тысяч новостных статей и сотнями тысяч зарегистрированных пользователей (и многие другие незарегистрированные посетители). Всегда отлично работал для наших нужд, и у нас никогда не было проблем с производительностью. - person mastazi; 13.08.2020

Упрощенная версия, использующая только 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. Нам не нужен составной полнотекстовый индекс.

person lubosdz    schedule 08.07.2017
comment
Используя HAVING вместо WHERE (с композитом или чем-то еще), вы сталкиваетесь с проблемой необходимости выполнить полное сканирование таблицы, чтобы получить результат. Это означает, что я не верю, что это решение очень хорошо масштабируется. Чтобы быть более конкретным, в экстремальном сценарии, если у вас есть таблица с 10 миллионами строк, и только 999 совпадений (или n-1 из любого установленного вами ограничения), поскольку все строки будут возвращать результаты в вашем запросе, хотя и с нулевыми значениями, вам придется не только загружать всю таблицу, но и перебирать все 10 миллионов строк. - person conrad10781; 08.02.2021
comment
@ conrad10781 Предложение Наличие работает только с совпавшим набором результатов. - person lubosdz; 08.02.2021
comment
правильно, но буквально каждая запись в таблице будет сопоставляться в этом запросе, потому что фильтровать нечего. Это означает, что вы выбираете значения из таблицы, но без где вы извлекаете все записей, а затем применяет к ним фильтр. Чтобы внести ясность, удалите оператор наличия из локального поиска. Все записи возвращаются. Представьте, что это таблица с 10 миллионами записей. Запустите объяснение, и оно, вероятно, скажет использование временного; с помощью файловой сортировки. Где, как и в ответе mintywalker, записи сначала фильтруются на сервере. - person conrad10781; 09.02.2021
comment
@ conrad10781 conrad10781 Да, вы правы - без предложения where он сканирует весь набор результатов. Идея заключалась в том, чтобы избежать сложного полнотекстового индексирования, которое может вызвать большие накладные расходы при интенсивной записи. Исправить это просто можно, добавив предложение WHERE между FROM ... HAVING, но тогда весь запрос уже не выглядит таким простым + дублирует совпадение полного индекса. Запрос выше может нормально работать для небольших наборов данных, скажем, до 10-100 тыс. - person lubosdz; 10.02.2021

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

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

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

person Community    schedule 17.02.2009

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

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

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
person Tom    schedule 10.08.2009

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

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

person adamJLev    schedule 13.02.2009

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

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

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

person Davide    schedule 16.02.2009

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

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

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

person dasplann    schedule 01.02.2011

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

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

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

person notnot    schedule 13.02.2009
comment
Я попробовал это и получил синтаксические ошибки. Я не думаю, что знал, что поставить в порядке бла-бла-спота. Предложения? - person Buzz; 14.02.2009
comment
Извините, это не было примером копирования и вставки. Порядок by в предложении over — это порядок, в котором вы применяете номера строк, поэтому он должен быть таким, каким вы обычно упорядочиваете результаты. - person notnot; 14.02.2009
comment
Теперь, когда я думаю об этом, этот будет дублировать записи, которые соответствуют как ключевому слову, так и содержанию. - person notnot; 14.02.2009
comment
Я не могу найти способ сделать эту работу. На самом деле, я не думаю, что mysql поддерживает row_number. - person Buzz; 16.02.2009