Pourquoi la recherche en texte intégral renvoie moins de lignes que LIKE

10

Je ne fais pas fonctionner la recherche en texte intégral comme je le souhaite, et je ne comprends pas les différences dans les listes de résultats.

Exemples d'instructions:

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

renvoie 92 lignes. Je reçois des lignes qui ont des correspondances, par exemple, comme "Punkten", "Zwei-Punkte-Vorsprung" et "Treffpunkt" dans la colonne meldungstext.

J'ai défini un index de texte intégral sur la colonne "meldungstext" et j'ai essayé ceci:

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

cela ne renvoie que 8 lignes. Je ne reçois que des lignes qui correspondent à "Punkt" lui-même ou des mots que je pense être considérés comme "Punkt" comme dans "i-Punkt".

J'ai ensuite essayé le mode booléen:

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

renvoie 44 lignes. Je reçois des lignes qui contiennent "Zwei-Punkte-Vorsprung" ou "Treffpunkt" dans la colonne meldungstext, mais pas celles avec "Punkten".

Pourquoi cela se produit-il et comment puis-je définir une recherche de texte intégral "pleinement" fonctionnelle pour empêcher l'utilisation de LIKE '%%' dans la clause where?

32bitfloat
la source
1
Cela mérite un gros +1 car ce problème n'est pas vraiment examiné et l'indexation FULLTEXT est souvent considérée comme acquise.
RolandoMySQLDBA

Réponses:

13

J'ai pris les trois chaînes dans votre question et je l'ai ajouté à un tableau plus trois autres chaînes avec panktau lieu de punkt.

Ce qui suit a été exécuté à l'aide de MySQL 5.5.12 pour 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>

J'ai exécuté ces requêtes sur la table en utilisant 3 approches différentes

  • MATCH ... AGAINST
  • LOCATEcomme dans la fonction LOCATE
  • LIKE

Veuillez noter les différences

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>

Toutes les valeurs de PunktMatch doivent être de 3 1 et 3 0.

Maintenant, regardez-moi les interroger comme d'habitude

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 en utilisant MATCH .. CONTRE avec punkt ne fonctionne pas. Et 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 BYLançons ma grande requête contre 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>

C'est faux aussi parce que je devrais voir 3 0 et 3 1 pour PanktMatch.

J'ai essayé autre chose

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>

J'ai ajouté un signe plus à pankt et j'ai obtenu des résultats différents. Que 2 et non 3 ???

Selon la documentation MySQL , notez ce qu'il dit sur le caractère générique:

*

L'astérisque sert d'opérateur de troncature (ou de caractère générique). Contrairement aux autres opérateurs, il doit être ajouté au mot à affecter. Les mots correspondent s'ils commencent par le mot précédant l'opérateur *.

Si un mot est spécifié avec l'opérateur de troncature, il n'est pas supprimé d'une requête booléenne, même s'il est trop court (comme déterminé à partir du paramètre ft_min_word_len) ou d'un mot d'arrêt. Cela se produit car le mot n'est pas considéré comme trop court ou un mot d'arrêt, mais comme un préfixe qui doit être présent dans le document sous la forme d'un mot qui commence par le préfixe. Supposons que ft_min_word_len = 4. Ensuite, une recherche de '+ mot + le *' retournera probablement moins de lignes qu'une recherche de '+ mot + le':

L'ancienne requête reste telle quelle et requiert que le mot et le * (un mot commençant par le) soient présents dans le document.

Cette dernière requête est transformée en + mot (ne nécessitant que le mot pour être présent). le est à la fois trop court et un mot d’arrêt, et l’une ou l’autre condition suffit pour qu’elle soit ignorée.

Sur cette base, le caractère générique est applicable pour le dos des jetons et non pour le devant. À la lumière de cela, la sortie doit être correcte car 2 des 3 jetons de démarrage du punkt. Même histoire avec pankt. Cela explique au moins pourquoi 2 sur 3 et pourquoi moins de lignes.

RolandoMySQLDBA
la source
Wow, merci beaucoup pour votre investissement. Cela signifie que la recherche en texte intégral fonctionne comme prévu, ou du moins comme indiqué dans le document. Mais cela indique également que l'ensemble du problème de texte intégral n'aidera pas à trouver 100% des colonnes qui incluent une partie de mot donnée, ce qui le rend inutile pour mes besoins. Pour des résultats exacts, je devrais rechercher avec LIKE ou LOCALE, qui, en outre, étonnamment, les deux semblent plus rapides.
32bitfloat
Pourquoi avez-vous trouvé "Punkten" et pas @ 32bitfloat?! Au lieu de cela, il a trouvé "Treffpunkt", mais vous ne l'avez pas fait. Et je ne comprends pas vraiment pourquoi "punkt" a renvoyé "Pankten" dans la COUNT(IF(MATCHrequête.
mgutt
Je me demande ce qui se passe dans InnoDB.
Rick James
Pourquoi avez-vous COUNT(…)sur les colonnes PunktMatch et PanktMatch? COUNT(IF(MATCH (meldungstext ) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0))se traduira toujours par 1, car il compte 1ou 0, le résultat de la IF(…).
Quinn Comendant