Поиск в MySQL. Часть 3 «FULLTEXT IN BOOLEAN MODE»

Поиск с учетом русской морфологии

Поиск в . Часть 3 « IN BOOLEAN MODE»

В первой части рассказа о поиске в MySQL рассказывается про использование полнотекстного индекса FULLTEXT. При поиске неких ключевых слов в большом массиве текста, хранящегося в БД, без использования индекса не обойтись. Однако родные возможности полнотекстового поиска в СУБД MySQL не обеспечиват функционал для поиска с учетом русской морфологии. Решение этой проблемы описывалось во второй части «Поиск с учетом русской морфологии». И вот недавно в описанном алгоритме обнаружился большой недостаток. Что это за недостаток и как с ним бороться и описано в этой статье.

Для начала хочу заметить один нюанс. Не так давно вышел релиз СУБД PostgreSQL 8.3. И одной из важнейших его особенностей стало то, что полнотекстный поиск, причем еще и с поддержкой русской морфологии, включен в ядро СУБД. Теперь не надо использовать различные плагины и примочки, чтобы пользоваться возможностями полнотекстного о поиска. А возможности эти на порядок мощнее и щире возможностей fulltext-поиска в MySQL. По-этому, если поиск информации в базе для вашего проекта является одной из центральных «фишек», стоит смотреть в сторону Postgres.

Если же у вас нет выбора СУБД, или требования к поиску невысокие, можно пользоваться и моим решением на базе MySQL. Только необходимо учесть поправки для поискового запроса, описанные ниже.

По делу. При использовании конструкции MATCH … AGAINST СУБД ищет указанные в запросе ключевые слова в индексе, если подходящий найден, или в указанном поле данных. Поиск может проходить в трех разных режимах: в натуральном (обычном) и логическом (boolean mode). Общее между этими режимами то, что в обоих случаях фраза поиска, переданная в AGAINST будет разделена на слова. И при поиске будут находиться записи, в которых встречается хотя бы одно из указанных слов.

Например, при указании фразы «трудовой договор» будут найдены все строки, в которых встречается слово «трудовой» и все строки со словом «договор». На практике такой подход оказывается неприемлемым, так как выдает слишком много лишних результатов. Особенно ярко выражается проблема, когда одно слово очень популярное и находится в огромном количестве строк. В этом случае уточняющее слово практически не имеет смысла. И релевантность тут не спасет. Популярное слово может встретиться в строке 50 раз. В этом случае строка получит релевантность значительно выше, чем строка в которой оба слова встретились по одному разу.

В результате поиск по такому принципу в большом массиве информации становится неюзабельным. Обойти проблему можно с помощью второго режима поиска — поиска в логическом режиме. Его преимущество в том, что с помощью некоторых специальных операторов можно управлять значимостью и ролью слов в запросе, а также искать по фразам.

Для включения логического режима используется синтаксис: MATCH (…) AGAINST (‘…’ IN BOOLEAN MODE);

Оператор пишется в кавычках непосредственно перед ключевым словом, к которому он относится. Операторы, которые воспринимает анализатор запроса:

  • — (дефис, символизирующий «минус»). Этот оператор указывает, что данное ключевое слово не должно содержаться в поле (NOT);
  • + указывает, что слово должно обязательно содержаться в запросе (AND);
  • > и < соответственно увеличивают и уменьшают вес слова при подсчете релевантности;
  • ( и ) группируют слова для применения к ним оператора
  • ~ имеет смысл, схожий с «минусом», но оно слово не исключает, а просто уменьшает релевантность строки, в которой слово встретилось;
  • * — оператор усечения, ставится в конце слова и имеет классический смысл (например, слов*);
  • » (кавычка). В кавычки берутся фразы, по которым требуется искать целиком.

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

Чтобы обеспечить точный поиск и релевантность вывода, передадим в запрос ключевые слова в обработанном виде. Первой частью пустим в кавычках фразу, составленную из слов, которые ввел пользователь (естественно, предварительно отфильтровав спец-символы). Установим перед ней оператор повышения релевантности «>». Далее через пробел открываем скобки. В них нужно указать слова, полученные от морфологического движка. Перед скобками ставим оператор уменьшения релевантности. Слова пишутся через пробел, а перед каждым словом ставится оператор «+».

Для наглядности приведем пример. При поиске по фразе «отпуск за работу» запрос будет выглядеть так: … MATCH (…) AGAINST (‘>»отпуск за работу» <(+отпуск +работу)’ IN BOOLEAN MODE) …

Кроме этого, из-за логического режима поиска необходимо вручную указать порядок вывода (сортировку) найденных строк. Для этого добавляем в выборку еще один столбец, который определяется так выражением MATCH … AGAINST с псевдонимом. Причем оно должно быть идентично выражению в блоке WHERE. Тогда СУБД будет выполнять поиск только один раз. Ну а в конец запроса нужно дописать, что сортировка идет по полю (через псевдоним) в порядке убывания.

Итак, теперь пример полного результирующего запроса. Поиск по фразе «отпуск за работу» в таблице table в полях text и text_index, по которым был предварительно создан полнотекстный индекс:

SELECT _CALC_FOUND_ROWS *,
MATCH (text,text_index) AGAINST (‘>»отпуск за работу» <(+отпуск +работа)’ IN BOOLEAN MODE) AS rel
FROM table
WHERE MATCH (text,text_index) AGAINST (‘>»отпуск за работу» <(+отпуск +работа)’ IN BOOLEAN MODE)
ORDER BY rel DESC LIMIT 0, 10

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

Поиск в MySQL. Часть 3 «FULLTEXT IN BOOLEAN MODE»: 14 комментариев

  1. Здравствуйте. У меня к вам вопрос может ли подобный механизм использоваться при поиске например по 100 сайтам?

  2. Это механиз поиска не по сайту, а по таблице-индексу. Чем будет заполнена эта таблица, не важно. Т.е. можно и поиск по одному сайту, и по многим сайтам, и по некоторому абстрактному набору информации (статьи, комментарии, …).

  3. > Чтобы обеспечить точный поиск и релевантность вывода, передадим в запрос ключевые слова в обработанном виде.
    Вопрос — откуда взять ключевые слова в обработанном виде?

  4. > > Чтобы обеспечить точный поиск и релевантность вывода, передадим в запрос ключевые слова в обработанном виде.
    > Вопрос — откуда взять ключевые слова в обработанном виде?

    Их генерирует phpmorphy. Читайте предыдущие части.и

  5. Вы как-нибудь решили проблему поиска двух-трех буквенных слов?
    Или сокращения ЦИК, РФ, УК, МВД и т.д. у вас по-прежнему игнорируются?

  6. Валерий, спасибо вам за этот алгоритм! Интересно узнать была ли с 8-го года его какаято эволюция?

  7. Нет, я давно перестал пользоваться этим способом, т.к. появилась возможность использовать специальные средства полнотекстового поиска, которые, как не крути, работают лучше.

  8. если можно расскажите немного о этих специальных средствах. П.С. Ваш алгоритм неплохо работает на хостингах которые о русском mysql fulltext знают только по наслышке.

  9. Что можно поднять на шаред-хостинге, я не знаю. А вообще, есть из бесплатных Lucene, Sphinx, в PostgreSQL теперь есть вростроенный (вроде) нормальный поиск, Яндекс.Сервер (кстати, весьма неплох, но ограничен в функционале). Есть несколько платных. У меня на работе юзается dtSearch — довольно мощный, но проблем с ним хватает.

  10. В статье описано как повысить рейтинг некоторым словам из поискового запроса. А можно ли наоборот: повысить рейтинг некоторым словам в тексте. Мне надо, что бы поиск искал не только по полю message_text, но и по message_title. Причём, те слова которые в тайтле — должны иметь бОльший коэффициент.

    Ну и на всякий случай освещу свою проблему целиком: есть собирательная таблица `search` в которую складываются все записи разных сущностей (новости, статьи, видео, …) В таблице есть универсальное поле `full_text` по которому осуществляется поиск. Все сущности складывают весь свой контент в это поле. И каждая сущность делает это по своему, например, от одной статьи туда попадает: $article[‘name’] .’ ‘. $article[‘anonce’] .’ ‘. $article[‘text’]. А у видео нет ни анонса, ни текста — есть только название.
    В итоге я хочу: что бы при поиске, первым выводились записи для которых ключевое слово было найдено в заголовках, ну а потом уж всё остальное. Если такой возможности нет, то есть ли она в Сфинксе?

  11. sverel, сделать то, что вы хотите, невозможно ни в какой поисковой системе. Чтобы регулировать вес полей, нужно эти поля разделять в базе и в индексе. Регулировать вес полей (атрибутов) умеет почти любая современная система полнотекстового поиска. Уверен, что Sphinx умеет. Пока все слеплено в одно поле, ничего у вас не выйдет.

  12. А как насчет такого запроса : ‘>»отпу* за раб*» <(+отпу* +раб*)’ ?
    Приемлимый?
    Вдруг человек пропустит какие-то буквы в конце или специально наберет сокращенно.
    Было бы здорово, если бы работало и отсечение начала, например: *тпус*

Добавить комментарий