Почему полнотекстовый поиск возвращает меньше строк, чем LIKE

10

У меня не работает полнотекстовый поиск, как я хочу, и я не понимаю различий в результирующих списках.

Пример заявления:

SELECT `meldungstext`
FROM `artikel`
WHERE `meldungstext` LIKE '%punkt%'

возвращает 92 строки. Я получаю строки, которые имеют совпадения, например, такие как «Punkten», «Zwei-Punkte-Vorsprung» и «Treffpunkt» в столбце meldungstext.

Я установил полнотекстовый индекс в столбце «meldungstext» и попробовал это:

SELECT `meldungstext`
FROM `artikel`
WHERE MATCH (`meldungstext`)
AGAINST ('*punkt*')

это возвращает только 8 строк. Я получаю только те строки, которые соответствуют самому «Punkt» или словам, которые, я думаю, воспринимаются как «Punkt», как в «i-Punkt».

Затем я попробовал логический режим:

SELECT `meldungstext`
FROM `artikel`
WHERE MATCH (`meldungstext`)
AGAINST ('*punkt*' IN BOOLEAN MODE)

возвращает 44 строки Я получаю строки, у которых есть "Zwei-Punkte-Vorsprung" или "Treffpunkt" в столбце meldungstext, но не те с "Punkten".

Почему это происходит и как я могу установить «полностью» работающий полнотекстовый поиск, чтобы не использовать LIKE «%%» в предложении where?

32bitfloat
источник
1
Это заслуживает большого +1, потому что эта проблема на самом деле не рассматривается, и индексация FULLTEXT часто принимается как должное.
RolandoMySQLDBA

Ответы:

13

Я взял три строки в вашем вопросе и добавил их в таблицу плюс еще три строки panktвместо punkt.

Следующее было выполнено с использованием MySQL 5.5.12 для Windows

mysql> CREATE TABLE artikel
    -> (
    ->     id INT NOT NULL AUTO_INCREMENT,
    ->     meldungstext MEDIUMTEXT,
    ->     PRIMARY KEY (id),
    ->     FULLTEXT (meldungstext)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO artikel (meldungstext) VALUES
    -> ('Punkten'),('Zwei-Punkte-Vorsprung'),('Treffpunkt'),
    -> ('Pankten'),('Zwei-Pankte-Vorsprung'),('Treffpankt');
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql>

Я провел эти запросы к таблице, используя 3 разных подхода

  • MATCH ... AGAINST
  • LOCATEкак в функции LOCATE
  • LIKE

Пожалуйста, обратите внимание на различия

mysql> SELECT id,meldungstext,
    -> COUNT(IF(MATCH (`meldungstext`) AGAINST ('*punkt*' IN BOOLEAN MODE),1,0)) PunktMatch,
    -> IF(LOCATE('punkt',meldungstext)>0,1,0) PunktLocate,
    -> meldungstext  LIKE '%punkt%' PunktLike
    -> FROM `artikel` GROUP BY id,meldungstext;
+----+-----------------------+------------+-------------+-----------+
| id | meldungstext          | PunktMatch | PunktLocate | PunktLike |
+----+-----------------------+------------+-------------+-----------+
|  1 | Punkten               |          1 |           1 |         1 |
|  2 | Zwei-Punkte-Vorsprung |          1 |           1 |         1 |
|  3 | Treffpunkt            |          1 |           1 |         1 |
|  4 | Pankten               |          1 |           0 |         0 |
|  5 | Zwei-Pankte-Vorsprung |          1 |           0 |         0 |
|  6 | Treffpankt            |          1 |           0 |         0 |
+----+-----------------------+------------+-------------+-----------+
6 rows in set (0.01 sec)

mysql>

Все значения PunktMatch должны быть 3 1 и 3 0.

Теперь смотри, как я опрашиваю их как обычно

mysql> SELECT `meldungstext` FROM `artikel`
    -> WHERE MATCH (`meldungstext`) AGAINST ('*punkt*' IN BOOLEAN MODE);
+-----------------------+
| meldungstext          |
+-----------------------+
| Zwei-Punkte-Vorsprung |
| Punkten               |
+-----------------------+
2 rows in set (0.01 sec)

mysql> SELECT `meldungstext` FROM `artikel`
    -> WHERE LOCATE('punkt',meldungstext)>0;
+-----------------------+
| meldungstext          |
+-----------------------+
| Punkten               |
| Zwei-Punkte-Vorsprung |
| Treffpunkt            |
+-----------------------+
3 rows in set (0.00 sec)

mysql> SELECT `meldungstext` FROM `artikel`
    -> WHERE `meldungstext` LIKE '%punk%';
+-----------------------+
| meldungstext          |
+-----------------------+
| Punkten               |
| Zwei-Punkte-Vorsprung |
| Treffpunkt            |
+-----------------------+
3 rows in set (0.00 sec)

mysql>

ОК, используя MATCH .. ПРОТИВ с punkt не работает. Что насчет pankt ???

mysql> SELECT `meldungstext` FROM `artikel` WHERE `meldungstext` LIKE '%pankt%';
+-----------------------+
| meldungstext          |
+-----------------------+
| Pankten               |
| Zwei-Pankte-Vorsprung |
| Treffpankt            |
+-----------------------+
3 rows in set (0.00 sec)

mysql>

Давайте запустим мой большой GROUP BYзапрос против pankt

mysql> SELECT id,meldungstext,
    -> COUNT(IF(MATCH (`meldungstext`) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0)) PanktMatch,
    -> IF(LOCATE('pankt',meldungstext)>0,1,0) PanktLocate,
    -> meldungstext  LIKE '%pankt%' PanktLike
    -> FROM `artikel` GROUP BY id,meldungstext;
+----+-----------------------+------------+-------------+-----------+
| id | meldungstext          | PanktMatch | PanktLocate | PanktLike |
+----+-----------------------+------------+-------------+-----------+
|  1 | Punkten               |          1 |           0 |         0 |
|  2 | Zwei-Punkte-Vorsprung |          1 |           0 |         0 |
|  3 | Treffpunkt            |          1 |           0 |         0 |
|  4 | Pankten               |          1 |           1 |         1 |
|  5 | Zwei-Pankte-Vorsprung |          1 |           1 |         1 |
|  6 | Treffpankt            |          1 |           1 |         1 |
+----+-----------------------+------------+-------------+-----------+
6 rows in set (0.01 sec)

mysql>

Это также неправильно, потому что я должен видеть 3 0 и 3 1 для PanktMatch.

Я пробовал что-то еще

mysql> SELECT id,meldungstext, MATCH (`meldungstext`) AGAINST ('+*pankt*' IN BOOLEAN MODE) PanktMatch, IF(LOCATE('pankt',meldungstext)>0,1,0) PanktLocate, meldungstext  LIKE '%pankt%' PanktLike FROM `artikel` GROUP BY id,meldungstext;
+----+-----------------------+------------+-------------+-----------+
| id | meldungstext          | PanktMatch | PanktLocate | PanktLike |
+----+-----------------------+------------+-------------+-----------+
|  1 | Punkten               |          0 |           0 |         0 |
|  2 | Zwei-Punkte-Vorsprung |          0 |           0 |         0 |
|  3 | Treffpunkt            |          0 |           0 |         0 |
|  4 | Pankten               |          1 |           1 |         1 |
|  5 | Zwei-Pankte-Vorsprung |          1 |           1 |         1 |
|  6 | Treffpankt            |          0 |           1 |         1 |
+----+-----------------------+------------+-------------+-----------+
6 rows in set (0.00 sec)

mysql>

Я добавил знак плюс в pankt и получил разные результаты. Что 2 а не 3 ???

В соответствии с документацией MySQL обратите внимание на то, что говорится о подстановочных знаках:

*

Звездочка служит оператором усечения (или подстановочного знака). В отличие от других операторов, он должен быть добавлен к слову, которое будет затронуто. Слова совпадают, если они начинаются со слова, предшествующего оператору *.

Если слово указано с помощью оператора усечения, оно не удаляется из логического запроса, даже если оно слишком короткое (как определено в настройке ft_min_word_len) или стоп-слово. Это происходит потому, что слово рассматривается не как слишком короткое или стоп-слово, а как префикс, который должен присутствовать в документе в форме слова, начинающегося с префикса. Предположим, что ft_min_word_len = 4. Тогда поиск по «+ word + the *», скорее всего, вернет меньше строк, чем поиск по «+ word + the»:

Первый запрос остается без изменений и требует наличия в документе как слова, так и * (слова, начинающиеся с).

Последний запрос преобразуется в + word (требуется только слово). и слишком короткое, и стоп-слово, и любого условия достаточно, чтобы его игнорировать.

Исходя из этого, подстановочный знак применим для задней части токенов, а не для передней. В свете этого вывод должен быть правильным, потому что 2 из стартовых токенов 3-х пунктов. Та же самая история с pankt. Это как минимум объясняет, почему 2 из 3 и почему меньше строк.

RolandoMySQLDBA
источник
Вау, большое спасибо за ваши инвестиции. Это означает, что полнотекстовый поиск работает в соответствии с требованиями или, по крайней мере, как указано в документе. Но это также говорит о том, что весь полнотекстовый выпуск не поможет найти 100% столбцов, которые содержат данную часть слова, что делает его бесполезным для моих целей. Для получения точных результатов мне нужно искать с помощью LIKE или LOCALE, которые, к удивлению, оба выглядят быстрее.
32-битное плавание
Почему вы нашли "Punkten", а @ 32bitfloat - нет ?! Вместо этого он нашел «Treffpunkt», а вы нет. И я не очень понимаю, почему «punkt» вернул «Pankten» в COUNT(IF(MATCHзапросе.
mgutt
Интересно, что происходит в InnoDB.
Рик Джеймс
Почему у вас есть COUNT(…)столбцы PunktMatch и PanktMatch? COUNT(IF(MATCH (meldungstext всегда) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0)) будет результатом , потому что он считает или , результат из . 110IF(…)
Куинн Комендант