Por qué la búsqueda de texto completo devuelve menos filas que LIKE

10

No hago que la búsqueda de texto completo funcione como quiero, y no entiendo las diferencias en las listas de resultados.

Declaraciones de ejemplo:

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

devuelve 92 filas. Recibo filas que tienen coincidencias, por ejemplo, como "Punkten", "Zwei-Punkte-Vorsprung" y "Treffpunkt" en la columna meldungstext.

Configuré un índice de texto completo en la columna "meldungstext" e intenté esto:

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

esto devuelve solo 8 filas. Solo recibo filas que coinciden con "Punkt" o palabras que creo que se toman como "Punkt" como en "i-Punkt".

Luego probé el modo booleano:

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

devuelve 44 filas. Recibo filas que tienen "Zwei-Punkte-Vorsprung" o "Treffpunkt" en la columna meldungstext, pero no las que tienen "Punkten".

¿Por qué sucede esto y cómo puedo establecer una búsqueda de texto completo que funcione "completamente" para evitar usar LIKE '%%' en la cláusula where?

32bitfloat
fuente
1
Esto merece un gran +1 porque este problema no se examina realmente y la indexación FULLTEXT a menudo se da por sentado.
RolandoMySQLDBA

Respuestas:

13

Tomé las tres cadenas en su pregunta y las agregué a una tabla más tres cadenas más en panktlugar de punkt.

Lo siguiente se ejecutó usando MySQL 5.5.12 para 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>

Ejecuté estas consultas contra la mesa usando 3 enfoques diferentes

  • MATCH ... AGAINST
  • LOCATEcomo en la función LOCATE
  • LIKE

Tenga en cuenta las diferencias.

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>

Todos los valores de PunktMatch deben ser 3 1 y 3 0.

Ahora mírame consultarlos normalmente

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 usando MATCH .. CONTRA con punkt no funciona. ¿Qué hay de 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 BYEjecutemos mi gran consulta contra 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>

Esto también está mal porque debería ver 3 0 y 3 1 para PanktMatch.

Probé otra cosa

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>

Agregué un signo más a pankt y obtuve resultados diferentes. ¿Qué 2 y no 3?

De acuerdo con la documentación de MySQL , observe lo que dice sobre el carácter comodín:

* *

El asterisco sirve como operador de truncamiento (o comodín). A diferencia de los otros operadores, debe agregarse a la palabra que se verá afectada. Las palabras coinciden si comienzan con la palabra que precede al operador *.

Si se especifica una palabra con el operador de truncamiento, no se elimina de una consulta booleana, incluso si es demasiado corta (según lo determinado por la configuración ft_min_word_len) o una palabra de parada. Esto ocurre porque la palabra no se ve como demasiado corta o como una palabra de parada, sino como un prefijo que debe estar presente en el documento en forma de una palabra que comienza con el prefijo. Supongamos que ft_min_word_len = 4. Entonces, una búsqueda de '+ word + the *' probablemente arrojará menos filas que una búsqueda de '+ word + the':

La consulta anterior permanece tal cual y requiere que tanto la palabra como el * (una palabra que comienza con) estén presentes en el documento.

La última consulta se transforma en + palabra (que requiere que solo esté presente la palabra). es demasiado corto y una palabra de parada, y cualquiera de las dos condiciones es suficiente para hacer que se ignore.

En base a esto, el carácter comodín es aplicable para el reverso de los tokens y no para el frente. A la luz de esto, la salida debe ser correcta porque 2 de los 3 tokens de inicio del punkt. La misma historia con pankt. Esto al menos explica por qué 2 de 3 y por qué menos filas.

RolandoMySQLDBA
fuente
Wow, muchas gracias por tu inversión. Esto significa que la búsqueda de texto completo funciona como se espera, o al menos como se dice en el documento. Pero esto también indica que todo el tema de texto completo no ayudará a encontrar el 100% de las columnas que incluyen una parte de palabra dada, lo que lo hace inútil para mis propósitos. Para obtener resultados exactos, necesitaría buscar con LIKE o LOCALE, que además, sorprendentemente, ambos parecen ser más rápidos.
32bitfloat
¿Por qué encontraste "Punkten" y @ 32bitfloat no ?! En cambio, encontró "Treffpunkt", pero tú no. Y realmente no entiendo por qué "punkt" devolvió "Pankten" en la COUNT(IF(MATCHconsulta.
mgutt
Me pregunto qué pasa en InnoDB.
Rick James
¿Por qué tienes COUNT(…)en las columnas PunktMatch y PanktMatch? COUNT(IF(MATCH (meldungstext siempre) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0)) dará como resultado , porque está contando o , el resultado de . 110IF(…)
Quinn Commandado