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