Dlaczego wyszukiwanie pełnotekstowe zwraca mniej wierszy niż LIKE

10

Wyszukiwanie pełnotekstowe nie działa tak, jak chcę, i nie rozumiem różnic w listach wyników.

Przykładowe instrukcje:

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

zwraca 92 wiersze. Otrzymuję wiersze, które pasują do siebie, na przykład „Punkten”, „Zwei-Punkte-Vorsprung” i „Treffpunkt” w kolumnie meldungstext.

Ustawiłem indeks pełnotekstowy w kolumnie „meldungstext” i spróbowałem:

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

zwraca to tylko 8 wierszy. Otrzymuję tylko wiersze, które pasują do samego „Punktu” lub słowa, które moim zdaniem są traktowane jako „Punkt” jak w „i-Punk”.

Następnie wypróbowałem tryb logiczny:

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

zwraca 44 wiersze. Otrzymuję wiersze, które mają „Zwei-Punkte-Vorsprung” lub „Treffpunkt” w kolumnie meldungstext, ale nie te z „Punkten”.

Dlaczego tak się dzieje i jak ustawić „w pełni działające” wyszukiwanie pełnotekstowe, aby zapobiec użyciu LIKE „%%” w klauzuli where?

32bitfloat
źródło
1
Zasługuje to na dużą +1, ponieważ ten problem nie jest tak naprawdę badany, a indeksowanie FULLTEXT jest często brane za pewnik.
RolandoMySQLDBA

Odpowiedzi:

13

Wziąłem trzy ciągi z twojego pytania i dodałem je do tabeli plus trzy kolejne z panktzamiast punkt.

Poniższe czynności zostały wykonane przy użyciu MySQL 5.5.12 dla systemu 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>

Uruchomiłem te zapytania do tabeli przy użyciu 3 różnych podejść

  • MATCH ... AGAINST
  • LOCATEjak w funkcji LOCATE
  • LIKE

Zwróć uwagę na różnice

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>

Wszystkie wartości PunktMatch powinny być równe 3 1 i 3 0.

Teraz patrz, jak pytam o nie jak zwykle

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 przy użyciu funkcji PODAJ. PONOWNIE z punktem nie działa. A co z panktem ???

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

mysql>

GROUP BYUruchommy moje duże zapytanie przeciwko 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>

Jest to również złe, ponieważ powinienem zobaczyć 3 0 i 3 1 dla PanktMatch.

Próbowałem czegoś innego

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>

Dodałem znak plus do pankt i uzyskałem różne wyniki. Co 2, a nie 3 ???

Zgodnie z Dokumentacją MySQL zwróć uwagę na to, co mówi o znaku wieloznacznym:

*

Gwiazdka służy jako operator obcięcia (lub symbolu wieloznacznego). W przeciwieństwie do innych operatorów, należy dodać do słowa, którego dotyczy zmiana. Słowa pasują, jeśli zaczynają się od słowa poprzedzającego operator *.

Jeśli słowo zostanie określone za pomocą operatora obcięcia, nie jest usuwane z zapytania boolowskiego, nawet jeśli jest ono zbyt krótkie (jak określono w ustawieniu ft_min_word_len) lub słowo kluczowe. Dzieje się tak, ponieważ słowo nie jest postrzegane jako zbyt krótkie lub słowo kluczowe, ale jako przedrostek, który musi występować w dokumencie w formie słowa rozpoczynającego się od przedrostka. Załóżmy, że ft_min_word_len = 4. Wtedy wyszukiwanie „+ słowo + słowo *” prawdopodobnie zwróci mniej wierszy niż wyszukiwanie „+ słowo + słowo”:

Poprzednie zapytanie pozostaje takie, jakie jest i wymaga, aby zarówno słowo, jak i * (słowo zaczynające się od) były obecne w dokumencie.

To ostatnie zapytanie jest przekształcane na słowo + (wymagające obecności tylko słowa). jest zarówno zbyt krótki, jak i słowo-stop, a każdy z warunków wystarcza, aby go zignorować.

Na tej podstawie znak wieloznaczny ma zastosowanie do tyłu znaczników, a nie do przodu. W świetle tego wynik musi być poprawny, ponieważ 2 z 3 żetonów początkowych punktu. Ta sama historia z panktem. To przynajmniej wyjaśnia, dlaczego 2 z 3 i dlaczego mniej wierszy.

RolandoMySQLDBA
źródło
Wow, wielkie dzięki za twoją inwestycję. Oznacza to, że wyszukiwanie pełnotekstowe działa zgodnie z oczekiwaniami lub przynajmniej tak, jak powiedziano w docu. Ale to również stwierdza, że ​​cały problem z pełnym tekstem nie pomoże znaleźć 100% kolumn zawierających dane słowo, co czyni go bezużytecznym dla moich celów. Aby uzyskać dokładne wyniki, musiałbym szukać za pomocą LIKE lub LOCALE, które oprócz zaskakująco oba wydają się być szybsze.
32bitfloat
Dlaczego znalazłeś „Punkten”, a @ 32bitfloat nie ?! Zamiast tego znalazł „Treffpunkt”, ale ty nie. I tak naprawdę nie rozumiem, dlaczego „punkt” zwrócił w COUNT(IF(MATCHzapytaniu „Pankten” .
mgutt
Zastanawiam się, co dzieje się w InnoDB.
Rick James
Dlaczego masz COUNT(…)w kolumnach PunktMatch i PanktMatch? COUNT(IF(MATCH (meldungstext zawsze) AGAINST ('*pankt*' IN BOOLEAN MODE),1,0)) będzie skutkować , ponieważ się liczy lub wynik z . 110IF(…)
Quinn Comendant