Чому повнотекстовий пошук повертає менше рядків, ніж 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 '%%' у пункті де?


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 .. ПРОТИ з пунктом не працює. А як щодо 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":

Колишній запит залишається таким, який є, і вимагає, щоб у документі були присутні і слово, і * (слово, що починається з).

Останній запит перетворюється на слово + (вимагає присутності лише слова). це і занадто коротке, і стоп-слово, і будь-якої умови достатньо, щоб ігнорувати його.

Виходячи з цього, символ підстановки застосовується для задньої частини жетонів, а не для передньої частини. Зважаючи на це, вихід повинен бути правильним, оскільки 2 з 3-х пускових жетонів пункту. Та ж історія з pankt. Це хоча б пояснює, чому 2 з 3 і чому менше рядків.


Вау, велике спасибі за ваші інвестиції. Це означає, що пошук у повнотекстових текстах працює як передбачено, або, принаймні, як сказано в документі. Але це також говорить про те, що вся повнотекстова тема не допоможе знайти 100% стовпців, які містять задану частину слова, що робить її марною для моїх цілей. Для точних результатів мені потрібно шукати LIKE або LOCALE, які крім дивовижних обох здаються швидшими.
32bitfloat

Чому ти знайшов "Punkten", а @ 32bitfloat не став ?! Натомість він знайшов "Treffpunkt", але ви цього не зробили. І я не дуже розумію, чому "punkt" повернув "Pankten" у COUNT(IF(MATCHзапиті.
mgutt

Цікаво, що відбувається в InnoDB.
Рік Джеймс

Чому у вас є COUNT(…)стовпці PunktMatch та PanktMatch? COUNT(IF(MATCH (meldungstext ) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0))буде завжди приводити 1, тому що це підрахунок 1або 0, результат від IF(…).
Квінн Комендант
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.