MySQL:如何有效地选择只有一个字符差异的字符串?

时间:2016-08-03 18:19:29

标签: mysql

在MySQL中是否可以从表中选择与给定字符串只有一个字符差异的所有字符串?

例如。 Iphone 5Iphone 5sModel x-1Models x1

1 个答案:

答案 0 :(得分:1)

如果您可以向MySQL添加用户功能,那么您可以使用Levenshtein的距离。有关代码,请参阅this other question

然后您可以查询WHERE LEVENSHTEIN(description, 'iphone 5') <= 2例如。你会发现“iphone 5S”,还有“ipohne 5”,这可能是一个加号。

否则,特定的案例很容易(例如,REGEX 'iphone.*'或类似),但一般情况下将是实施的噩梦。

性能

修改此版本以接受第三个参数MAXCOST。当达到levenshtein子环路的maxcost时,搜索功能将简单地中止。排除病态情况,并记住实际成本可能略有偏差(即不包括12的成本可能包括成本为12的字符串,并排除成本为11的字符串),这减少了的情况Levenshtein距离不合理。这些案件可能对您不感兴趣。

因此,如果您想要距离小于3的字符串,则可以使用WHERE levenshtein(string1, string2, 4) < 3。距离为3-4或以上的所有字符串现在将快速返回4 ,并被排除在外。

测试:

mysql> select BENCHMARK(1000,levenshtein('PIPPORIDICOLO','LUKASPERICOLO', 99));
+------------------------------------------------------------------+
| BENCHMARK(1000,levenshtein('PIPPORIDICOLO','LUKASPERICOLO', 99)) |
+------------------------------------------------------------------+
|                                                                0 |
+------------------------------------------------------------------+
1 row in set (2.50 sec)

mysql> select BENCHMARK(1000,levenshtein('PIPPORIDICOLO','LUKASPERICOLO', 3));
+-----------------------------------------------------------------+
| BENCHMARK(1000,levenshtein('PIPPORIDICOLO','LUKASPERICOLO', 3)) |
+-----------------------------------------------------------------+
|                                                               0 |
+-----------------------------------------------------------------+
1 row in set (0.08 sec)

此处,每1000次评估的成本从2500毫秒减少到80毫秒。

这是修改后的代码。除非更改名称,否则必须在定义新函数之前删除旧函数。

DELIMITER $$
CREATE FUNCTION levenshtein( s1 VARCHAR(255), s2 VARCHAR(255), maxcost INTEGER)
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 c > maxcost THEN
                 RETURN maxcost;
            END IF;
            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 ;