Mysql 全文搜索,自然语言模式:按 "closeness" 排序

Mysql Fulltext search, natural language mode: order by "closeness"

我正在使用 MYSQL 的全文搜索功能(在 Mysql 5.6.33 中)。

如果我在 NATURAL LANGUAGE 模式下进行匹配,对于邮政编码,有一个字符的拼写错误,我会得到一些不错的结果,包括带有 "right" 邮政编码的结果,但它们不是接近顶部。

例如,邮政编码为 "BN2 1TL" 的学校有 10 所。我故意把它拼错为 "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;

仔细观察,这是因为搜索已经回购了所有 "BN2""1TM" 在其 address_string 列中的结果,并且它们的分数完全相同,所以实际上是随机顺序。 .

这是完全合理的行为,但如果我能得到将 "closeness" 考虑在内的分数,那就太好了,这意味着,对于 "BN2 1TM" 上的搜索,"BN2 1TL"会比 "BN2 3PQ" 得分更高。有没有办法做到这一点?

EDIT:我记得这种类型的接近度在技术上称为"Levenshtein distance",这是对Levenshtein algorithm的参考,用于确定有多少替换是需要将一个字符串变成另一个字符串。所以我想我的问题可能是 "Can i get MYSQL FULLTEXT NATURAL LANGUAGE MODE scoring to take Levenshtein distance into account"?

首先,MySQL 全文在开放式搜索方面不如像 Lucene 这样的专用系统那么好。

有一种算法,称为 Levenshtein 距离,它计算字符转换的次数 - 距离 - 以将一个字符串更改为另一个字符串。

因此,将"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

然后,您只需要一个存储函数来计算 Levenshtein 距离。 (这不是内置于 FULLTEXT 中。)

来自this source,这里有这样一个存储函数。但要注意,它速度不快,而且不能使用索引。因此,如果您可以在执行此操作之前缩小搜索范围,您将获得更好的性能。

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 ;