Por que a pesquisa de texto completo retorna menos linhas que LIKE

10

Não faço a pesquisa de texto completo funcionar como eu quero e não entendo as diferenças nas listas de resultados.

Exemplos de instruções:

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

retorna 92 ​​linhas. Recebo linhas que possuem correspondências, por exemplo, como "Punkten", "Zwei-Punkte-Vorsprung" e "Treffpunkt" na coluna meldungstext.

Eu configurei um índice de texto completo na coluna "meldungstext" e tentei o seguinte:

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

isso retorna apenas 8 linhas. Eu recebo apenas linhas que correspondem ao "Punkt" ou palavras que considero "Punkt" como em "i-Punkt".

Eu tentei o modo booleano:

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

retorna 44 linhas. Recebo linhas com "Zwei-Punkte-Vorsprung" ou "Treffpunkt" na coluna meldungstext, mas não aquelas com "Punkten".

Por que isso acontece e como posso definir uma pesquisa de texto completo "totalmente" para impedir o uso de LIKE '%%' na cláusula where?

32bitfloat
fonte
11
Isso merece um grande +1, porque esse problema não é realmente examinado e a indexação do FULLTEXT geralmente é considerada um dado adquirido.
RolandoMySQLDBA

Respostas:

13

Peguei as três seqüências de caracteres em sua pergunta e a adicionei a uma tabela, mais três seqüências com, em panktvez de punkt.

O seguinte foi executado usando o 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>

Eu executei essas consultas na tabela usando 3 abordagens diferentes

  • MATCH ... AGAINST
  • LOCATEcomo na função LOCATE
  • LIKE

Observe as diferenças

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 os valores de PunktMatch devem conter 3 1 e 3 0.

Agora observe-me consultá-los 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 com punkt não funciona. E o pankt ???

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

mysql>

Vamos executar minha grande GROUP BYconsulta no 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>

Isso também está errado, porque eu deveria ver 3 0 e 3 1 no PanktMatch.

Eu tentei outra coisa

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>

Eu adicionei um sinal de mais ao pankt e obtive resultados diferentes. O que 2 e não 3 ??

De acordo com a documentação do MySQL , observe o que diz sobre o caractere curinga:

*

O asterisco serve como operador de truncamento (ou curinga). Diferente dos outros operadores, ele deve ser anexado à palavra a ser afetada. As palavras correspondem se começarem com a palavra que precede o operador *.

Se uma palavra é especificada com o operador de truncamento, ela não é retirada de uma consulta booleana, mesmo que seja muito curta (conforme determinado na configuração ft_min_word_len) ou uma palavra de parada. Isso ocorre porque a palavra não é vista como muito curta ou uma palavra de parada, mas como um prefixo que deve estar presente no documento na forma de uma palavra que começa com o prefixo. Suponha que ft_min_word_len = 4. Em seguida, uma pesquisa por '+ palavra + o *' provavelmente retornará menos linhas do que uma pesquisa por '+ palavra + o':

A consulta anterior permanece como está e exige que a palavra e o * (uma palavra começando com) estejam presentes no documento.

A última consulta é transformada em + word (exigindo que apenas a palavra esteja presente). o é muito curto e uma palavra de parada, e qualquer uma das condições é suficiente para fazer com que ela seja ignorada.

Com base nisso, o caractere curinga é aplicável ao verso dos tokens e não ao front. À luz disso, a saída deve estar correta porque 2 dos 3 tokens de inicialização do punkt. A mesma história com pankt. Isso pelo menos explica por que 2 de 3 e por que menos linhas.

RolandoMySQLDBA
fonte
Uau, muito obrigado pelo seu investimento. Isso significa que a pesquisa de texto completo funciona conforme o necessário, ou pelo menos como dito no documento. Mas isso também afirma que toda a edição de texto completo não ajudará a encontrar 100% das colunas que incluem uma determinada parte da palavra, o que a torna inútil para meus propósitos. Para resultados exatos, eu precisaria pesquisar com LIKE ou LOCALE, que além de surpreendentemente ambos parecem ser mais rápidos.
precisa saber é o seguinte
Por que você encontrou "Punkten" e @ 32bitfloat não ?! Em vez disso, ele encontrou "Treffpunkt", mas você não encontrou. E eu realmente não entendo por que "punkt" retornou "Pankten" na COUNT(IF(MATCHconsulta.
mgutt
Eu me pergunto o que acontece no InnoDB.
Rick James
Por que você tem COUNT(…)nas colunas PunktMatch e PanktMatch? COUNT(IF(MATCH (meldungstext ) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0))vai sempre resultar em 1, porque é a contagem 1ou 0, o resultado do IF(…).
Comendador Quinn