Как экранировать строку для полнотекстового поиска в MariaDB / MySQL - PullRequest
0 голосов
/ 01 сентября 2018

Существуют определенные символы (операторы), которые влияют на поведение полнотекстового поиска в MariaDB. Они +-<>()~*", а их функциональность описана в документации .

Я хочу иметь возможность искать слово, которое содержит один из этих операторов, и я хочу, чтобы MariaDB работала с ним как с обычным символом, а не с оператором. Как я могу это сделать?

Пример:

Давайте создадим таблицу с полнотекстовым индексом:

CREATE TABLE users (username TEXT, FULLTEXT(username)) ENGINE=InnoDB;

INSERT INTO users(username) VALUES ('joseph'), ('jose'), ('jose*');

Теперь я хочу найти строки, содержащие ровно jose*:

SELECT * FROM users WHERE MATCH(username) AGAINST('jose*' IN BOOLEAN MODE);
+----------+
| username |
+----------+
| joseph   |
| jose     |
| jose*    |
+----------+

Но я хочу только строку с jose*. Тот же самый результат, когда я пытаюсь избежать этой строки так, как я ожидал, она может сработать.

SELECT * FROM users WHERE MATCH(username) AGAINST('jose\*' IN BOOLEAN MODE);
+----------+
| username |
+----------+
| joseph   |
| jose     |
| jose*    |
+----------+

SELECT * FROM users WHERE MATCH(username) AGAINST('jose\\*' IN BOOLEAN MODE);
+----------+
| username |
+----------+
| joseph   |
| jose     |
| jose*    |
+----------+

Как правильно экранировать строку для полнотекстового поиска в MariaDB / MySQL?

1 Ответ

0 голосов
/ 02 сентября 2018

Полнотекстовый поиск - это инструмент для эффективного поиска слов (или начала слов), которые появляются в любом месте (полного) текста. Если ваши данные не содержат разделенных «слов» (каким бы способом вы их не определяли), полнотекстовый индекс не является подходящим инструментом для вашей задачи (поскольку он будет совершенно бесполезным). По умолчанию * - это разделитель слов, как и пробел (например, 'abc*def', а также 'abc def' - это два слова с двумя отдельными записями в полнотекстовом индексе, ни одно из которых не будет содержать *). , Вы можете указать, что вы хотите использовать в качестве разделителя, но MySQL не поддерживает его определение на лету, экранируя их в поисковом выражении; Вы должны сделать это при создании индекса, чтобы индекс фактически содержал jose*, а не только jose.

Если у вас нет слов (или очень ограниченный набор разделителей), вы можете использовать, например, username = 'jose*, username like 'jose*' или аналогичный; В качестве альтернативы, вы можете использовать регулярные выражения , которые являются медленными, но резервным инструментом для сложных требований (например, если полный текст не работает для вашей ситуации), когда полнотекстовый индекс не является usabel (и / или вы не можете измените конфигурацию в соответствии с вашими требованиями).

Чтобы изменить символы, которые MySQL рассматривает как разделитель, вы можете изменить карту символов, см. Добавление сопоставления для полнотекстовой индексации :

  • добавить новое сопоставление к index.xml
  • добавить это сопоставление в файл символов (например, latin1.xml) и отредактировать ctype, чтобы определить определенный символ как (не) разделитель; только для *, измените его на "48 10 10 10 10 10 10 10 10 10 01 10 10 10 10 10"); сделайте это для всех символов, которые вы хотите использовать для поиска (но помните, что если у вас нет хотя бы одного оставшегося разделителя, полнотекстовый поиск бесполезен).
  • после перезапуска используйте это сопоставление для своего столбца (например, ... (username TEXT collate 'latin1_fulltext_ci', ...) и заново создайте полнотекстовый индекс, и MySQL включит эти символы в индекс.
  • имейте в виду, что вам нужно делать это на каждом сервере, для которого вы хотите использовать это поведение

Теперь следующие три поиска должны вернуть ожидаемые результаты:

... MATCH(username) AGAINST('"jose*"' IN BOOLEAN MODE);

... MATCH(username) AGAINST('jose*');

... MATCH(username) AGAINST('"jose*"');

"..." будет искать точное совпадение (например, словосочетание); он работает аналогично экранированию, но не совсем, так как применяется только к символам без разделителей.

... MATCH(username) AGAINST('jose*' IN BOOLEAN MODE);

будет не работать для InnoDB (это будет рассматриваться как подстановочный знак), но будет работать для MyISAM (одно из тонких отличий между ними). ​​

Если вы действительно хотите использовать логический режим, но вам нужен подстановочный знак, отличный от *, вы можете определить другой символ подстановки, используя ft_boolean_syntax, хотя из-за ошибки в InnoDB это также работает только в MyISAM. Это также глобальная настройка, поэтому изменил бы поведение всех других полнотекстовых поисков в других таблицах (и базах данных). Возможно, вам придется указать, чего вы хотите достичь в этом режиме, чтобы увидеть, есть ли способ заставить полнотекстовый поиск работать с этими требованиями, но в конечном итоге вам, возможно, придется использовать like.

...