Warum die Volltextsuche weniger Zeilen als LIKE zurückgibt

10

Ich kann die Volltextsuche nicht so ausführen, wie ich es möchte, und ich verstehe die Unterschiede in den Ergebnislisten nicht.

Beispielanweisungen:

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

gibt 92 Zeilen zurück. Ich erhalte Zeilen, die Übereinstimmungen haben, wie zum Beispiel "Ereignisse", "Zwei-Punkte-Vorsprung" und "Treffpunkt" in der Spalte meldungstext.

Ich habe einen Volltextindex für die Spalte "meldungstext" gesetzt und Folgendes versucht:

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

Dies gibt nur 8 Zeilen zurück. Ich erhalte nur Zeilen, die mit "Punkt" selbst übereinstimmen, oder Wörter, die meiner Meinung nach als "Punkt" wie in "i-Punkt" verwendet werden.

Ich habe dann den Booleschen Modus ausprobiert:

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

gibt 44 Zeilen zurück. Ich erhalte Zeilen mit "Zwei-Punkte-Vorsprung" oder "Treffpunkt" in der Spalte meldungstext, aber nicht solche mit "bestimmten".

Warum passiert das und wie kann ich eine "voll funktionsfähige" Volltextsuche festlegen, um zu verhindern, dass LIKE '%%' in der where-Klausel verwendet wird?

32bitfloat
quelle
1
Dies verdient eine große +1, da dieses Problem nicht wirklich untersucht wird und die FULLTEXT-Indizierung häufig als selbstverständlich angesehen wird.
RolandoMySQLDBA

Antworten:

13

Ich habe die drei Zeichenfolgen in Ihrer Frage genommen und sie einer Tabelle hinzugefügt, plus drei weitere Zeichenfolgen mit panktanstelle von punkt.

Folgendes wurde mit MySQL 5.5.12 für Windows ausgeführt

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>

Ich habe diese Abfragen mit drei verschiedenen Ansätzen für die Tabelle ausgeführt

  • MATCH ... AGAINST
  • LOCATEwie in der LOCATE- Funktion
  • LIKE

Bitte beachten Sie die Unterschiede

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>

Alle PunktMatch-Werte sollten 3 1 und 3 0 sein.

Jetzt schau mir zu, wie ich sie wie gewohnt abfrage

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>

OK mit MATCH .. GEGEN mit Punkt funktioniert nicht. Was ist mit pankt ???

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

mysql>

Lassen Sie uns meine große GROUP BYAbfrage gegen pankt ausführen

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>

Dies ist auch falsch, weil ich 3 0er und 3 1er für PanktMatch sehen sollte.

Ich habe etwas anderes versucht

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>

Ich habe pankt ein Pluszeichen hinzugefügt und unterschiedliche Ergebnisse erzielt. Welche 2 und nicht 3 ???

Beachten Sie gemäß der MySQL-Dokumentation , was darin über das Platzhalterzeichen steht:

* *

Das Sternchen dient als Kürzungs- (oder Platzhalter-) Operator. Im Gegensatz zu den anderen Operatoren sollte es an das betroffene Wort angehängt werden. Wörter stimmen überein, wenn sie mit dem Wort vor dem Operator * beginnen.

Wenn ein Wort mit dem Kürzungsoperator angegeben wird, wird es nicht aus einer booleschen Abfrage entfernt, auch wenn es zu kurz (wie aus der Einstellung ft_min_word_len bestimmt) oder ein Stoppwort ist. Dies liegt daran, dass das Wort nicht als zu kurz oder als Stoppwort angesehen wird, sondern als Präfix, das im Dokument in Form eines Wortes vorhanden sein muss, das mit dem Präfix beginnt. Angenommen, ft_min_word_len = 4. Dann wird eine Suche nach '+ Wort + das *' wahrscheinlich weniger Zeilen zurückgeben als eine Suche nach '+ Wort + das':

Die vorherige Abfrage bleibt unverändert und erfordert, dass sowohl das Wort als auch das * (ein Wort, das mit dem beginnt) im Dokument vorhanden sind.

Die letztere Abfrage wird in + Wort umgewandelt (wobei nur Wort vorhanden sein muss). Das ist sowohl zu kurz als auch ein Stoppwort, und jede Bedingung reicht aus, um es zu ignorieren.

Auf dieser Grundlage gilt das Platzhalterzeichen für die Rückseite von Token und nicht für die Vorderseite. Vor diesem Hintergrund muss die Ausgabe korrekt sein, da 2 der 3 Punkt-Start-Token vorhanden sind. Gleiche Geschichte mit pankt. Dies erklärt zumindest, warum 2 von 3 und warum weniger Zeilen.

RolandoMySQLDBA
quelle
Wow, vielen Dank für Ihre Investition. Dies bedeutet, dass die Volltextsuche wie erwartet oder zumindest wie im Dokument angegeben funktioniert. Dies besagt aber auch, dass das gesamte Volltextproblem nicht dazu beiträgt, 100% der Spalten zu finden, die einen bestimmten Wortteil enthalten, was es für meine Zwecke unbrauchbar macht. Für genaue Ergebnisse müsste ich mit LIKE oder LOCALE suchen, die außerdem überraschenderweise beide schneller zu sein scheinen.
32bitfloat
Warum hast du "gefunden" gefunden und @ 32bitfloat nicht?! Stattdessen fand er "Treffpunkt", aber Sie haben es nicht getan. Und ich verstehe nicht wirklich, warum "punkt" in der COUNT(IF(MATCHAbfrage "Pankten" zurückgegeben hat .
mgutt
Ich frage mich, was in InnoDB passiert.
Rick James
Warum haben Sie COUNT(…)in den Spalten PunktMatch und PanktMatch? COUNT(IF(MATCH (Meldungstext ) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0))wird immer zur Folge 1, weil es das Zählen ist 1oder 0das Ergebnis aus der IF(…).
Quinn Comendant