Mysql Полнотекстовый поиск, режим естественного языка: упорядочение по "близости" - PullRequest
1 голос
/ 11 марта 2019

Я использую функциональность поиска в полном тексте MYSQL (в Mysql 5.6.33).

Если я выполню МАТЧ в режиме ЕСТЕСТВЕННОГО ЯЗЫКА для почтового индекса с односимвольной опечаткой, я получу несколько приличных результатов, включая результаты с «правильным» почтовым индексом, но они не находятся в верхней части.

Например, есть 10 школ с почтовым индексом "BN2 1TL". Я намеренно опишу это как "BN2 1TM" и выполняю поиск следующим образом:

SELECT record_id, address_string, 
  MATCH (address_string) AGAINST ("BN2 1TM" IN NATURAL LANGUAGE MODE) AS score 
  FROM schools 
  WHERE MATCH (address_string) AGAINST ("BN2 1TM" IN NATURAL LANGUAGE MODE) > 0 
  ORDER BY score DESC;

При ближайшем рассмотрении это связано с тем, что поиск выкупил все результаты, у которых в столбце address_string есть либо "BN2", либо "1TM", и все они имеют абсолютно одинаковую оценку и поэтому находятся в случайном порядке, эффективно , .

Это вполне разумное поведение, но было бы замечательно, если бы я мог получить оценку, учитывающую "близость", что означает, что при поиске по "BN2 1TM", "BN2 1TL" будет оцениваться выше, чем "BN2 3PQ". Есть ли способ сделать это?

РЕДАКТИРОВАТЬ : Я вспомнил, что этот тип близости технически называется «расстояние Левенштейна», что является ссылкой на алгоритм Левенштейна для определения количества замен, необходимых для поворота одного Струна в другой. Поэтому я думаю, что мой вопрос может звучать так: "Могу ли я получить оценку MYSQL FULLTEXT NATURAL LANGUAGE MODE, чтобы учесть расстояние Левенштейна" ?

1 Ответ

1 голос
/ 11 марта 2019

Во-первых, полный текст MySQL далеко не так хорош для открытого поиска, как выделенные системы, такие как Lucene.

Существует алгоритм, называемый Расстояние Левенштейна , который вычисляет количество преобразований символов - расстояние - для преобразования одной строки в другую.

Таким образом, изменение «BN2 1TM» на «BN2 1MT» (транспозиция) имеет расстояние 2. Изменение его на «BN2 1TX» имеет расстояние 1.

Расстояние Левенштейна не очень полезно для фраз, если они почти не совпадают. Замена «Apache Sphinx» на «MySQL FULLTEXT» дает расстояние 14, длину более длинной строки. Но это полезно для почтовых индексов, номеров и других коротких структурированных слов.

Вы можете попробовать что-то вроде этого, чтобы сначала получить ближайшие значения.

  SELECT city, county, postcode
    FROM table
   ORDER BY levenshtein(postcode, 'BN2 1MT') ASC

Тогда все, что вам нужно - это сохраненная функция для вычисления расстояний Левенштейна. (Это не встроено в FULLTEXT.)

Из этого источника , вот такая сохраненная функция. Но будьте осторожны, это не быстро и не может использовать индекс. Поэтому, если вы сможете сузить поиск до того, как вы это сделаете, вы получите лучшую производительность.

DELIMITER $$
CREATE FUNCTION levenshtein( s1 VARCHAR(255), s2 VARCHAR(255) )
    RETURNS INT
    DETERMINISTIC
    BEGIN
        DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
        DECLARE s1_char CHAR;
        -- max strlen=255
        DECLARE cv0, cv1 VARBINARY(256);

        SET s1_len = CHAR_LENGTH(s1), 
            s2_len = CHAR_LENGTH(s2), 
            cv1 = 0x00, 
            j = 1, 
            i = 1, 
            c = 0;

        IF s1 = s2 THEN
            RETURN 0;
        ELSEIF s1_len = 0 THEN
            RETURN s2_len;
        ELSEIF s2_len = 0 THEN
            RETURN s1_len;
        ELSE
            WHILE j <= s2_len DO
                SET cv1 = CONCAT(cv1, UNHEX(HEX(j))), j = j + 1;
            END WHILE;
            WHILE i <= s1_len DO
                SET s1_char = SUBSTRING(s1, i, 1), c = i, cv0 = UNHEX(HEX(i)), j = 1;
                WHILE j <= s2_len DO
                    SET c = c + 1;
                    IF s1_char = SUBSTRING(s2, j, 1) THEN
                        SET cost = 0; ELSE SET cost = 1;
                    END IF;
                    SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10) + cost;
                    IF c > c_temp THEN SET c = c_temp; END IF;
                    SET c_temp = CONV(HEX(SUBSTRING(cv1, j+1, 1)), 16, 10) + 1;
                    IF c > c_temp THEN
                        SET c = c_temp;
                    END IF;
                    SET cv0 = CONCAT(cv0, UNHEX(HEX(c))), j = j + 1;
                END WHILE;
                SET cv1 = cv0, i = i + 1;
            END WHILE;
        END IF;
        RETURN c;
    END$$
DELIMITER ;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...