实现MySQL /模糊searchLevenshtein距离?

我希望能够按如下方式search一个表格,以获得1个方差内的所有信息。

数据:

奥布莱恩
 Smithe
杜兰
 Smuth
皇
 Smoth
冈瑟
 Smiht

我已经考虑使用Levenshtein距离没有人知道如何实现这一点呢?

这有帮助吗? MySQL Levenshtein距离查询

编辑: 作为一个MySQL存储function(谷歌caching)的旧链接Levenshtein距离被打破,感谢罗伯特指出这在评论。

为了高效地使用levenshtein距离进行search,您需要一个高效的专用索引,例如bk-tree 。 不幸的是,我所知道的包括MySQL在内的数据库系统都没有实现bk-tree索引。 如果您正在寻找全文search,而不是每行只有一个词,这会变得更加复杂。 另一方面,我想不出有什么办法可以做全文索引的方式允许基于levenshtein距离的search。

damerau-levenshtein距离的实现可以在这里find: Damerau-Levenshteinalgorithm:Levenshtein与换位对纯Levenshtein距离的改进是考虑字符的交换。 我发现它在schnaader的链接的评论,谢谢!

有一个Levenshtein距离函数的MySQL UDF实现

https://github.com/jmcejuela/Levenshtein-MySQL-UDF

它以C语言实现,性能比schnaader提到的“MySQL Levenshtein距离查询”更好

以上levenshtein <= 1给出的函数是不正确的 – 它给出了例如“bed”和“bid”的错误结果。

我修改了上面给出的“MySQL Levenshtein距离查询”,在第一个答案中,接受一个“极限”,这会加快一点。 基本上,如果你只关心Levenshtein <= 1,设置极限为“2”,如果它是0或1,函数将返回精确的Levenshtein距离; 或者2,如果确切的levenshtein距离是2或更大。

这个mod使得search词速度提高15%到50% – search词越长,优势就越大(因为algorithm可以更早地提交)。例如,在search200,000个单词以find单词的距离1内的所有匹配“咯咯笑”,原来我的笔记本电脑需要3分47秒,而“极限”版本是1:39。 当然,这些对于实时使用来说都太慢了。

码:

DELIMITER $$ CREATE FUNCTION levenshtein_limit_n( s1 VARCHAR(255), s2 VARCHAR(255), n INT) RETURNS INT DETERMINISTIC BEGIN DECLARE s1_len, s2_len, i, j, c, c_temp, cost, c_min 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, c_min = 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 and c_min < n DO -- if actual levenshtein dist >= limit, don't bother computing it SET s1_char = SUBSTRING(s1, i, 1), c = i, c_min = 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; IF c < c_min THEN SET c_min = c; END IF; END WHILE; SET cv1 = cv0, i = i + 1; END WHILE; END IF; IF i <= s1_len THEN -- we didn't finish, limit exceeded SET c = c_min; -- actual distance is >= c_min (ie, the smallest value in the last computed row of the matrix) END IF; RETURN c; END$$ 

根据Gonzalo Navarro和Ricardo Baeza-yates撰写的一篇论文,我正在基于Levenshtein或Damerau-Levenshtein(可能是后者)对search索引文本进行search。

在build立一个后缀数组( 见维基百科 )后,如果你对最多与searchstring不匹配的string感兴趣,则将searchstring分解为k + 1个; 至less有一个必须是完整的。 通过二进制search在后缀数组中find子string,然后将距离函数应用到每个匹配的棋子周围的补丁。

你可以使用这个function


 CREATE FUNCTION`levenshtein`(s1 text,s2 text)RETURNS int(11)
    确定性
开始 
     DECLARE s1_len,s2_len,i,j,c,c_temp,cost INT; 
     DECLARE s1_char CHAR; 
     DECLARE cv0,cv1文本; 
     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 
      返回s2_len; 
     ELSEIF s2_len = 0 THEN 
      返回s1_len; 
    其他 
       WHILE j <= s2_len DO 
         SET cv1 = CONCAT(cv1,UNHEX(HEX(j))),j = j + 1; 
       END WHILE; 
      当我<= 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成本= 0;  ELSE SET cost = 1; 
          万一; 
           SET c_temp = CONV(HEX(SUBSTRING(cv1,j,1)),16,10)+ cost; 
           IF c> c_temp THEN SET c = c_temp; 万一; 
             SET c_temp = CONV(HEX(SUBSTRING(cv1,j + 1,1)),16,10)+1; 
             IF c> c_temp THEN  
               SET c = c_temp;  
            万一; 
             SET cv0 = CONCAT(cv0,UNHEX(HEX(c))),j = j + 1; 
         END WHILE; 
         SET cv1 = cv0,i = i + 1; 
       END WHILE; 
    万一; 
     RETURN c; 
  结束

并为XX%使用此function


 CREATE FUNCTION`levenshtein_ratio`(s1 text,s2 text)RETURNS int(11)
    确定性
开始 
     DECLARE s1_len,s2_len,max_len INT; 
     SET s1_len = LENGTH(s1),s2_len = LENGTH(s2); 
     IF s1_len> s2_len THEN  
       SET max_len = s1_len;  
    其他  
       SET max_len = s2_len;  
    万一; 
    返回圆((1-LEVENSHTEIN(s1,s2)/ max_len)* 100); 
  结束

如果您只想知道levenshtein距离是否至多为1,则可以使用以下MySQL函数。

 CREATE FUNCTION `lv_leq_1` ( `s1` VARCHAR( 255 ) , `s2` VARCHAR( 255 ) ) RETURNS TINYINT( 1 ) DETERMINISTIC BEGIN DECLARE s1_len, s2_len, i INT; SET s1_len = CHAR_LENGTH(s1), s2_len = CHAR_LENGTH(s2), i = 1; IF s1 = s2 THEN RETURN TRUE; ELSEIF ABS(s1_len - s2_len) > 1 THEN RETURN FALSE; ELSE WHILE SUBSTRING(s1,s1_len - i,1) = SUBSTRING(s2,s2_len - i,1) DO SET i = i + 1; END WHILE; RETURN SUBSTRING(s1,1,s1_len-i) = SUBSTRING(s2,1,s2_len-i) OR SUBSTRING(s1,1,s1_len-i) = SUBSTRING(s2,1,s2_len-i+1) OR SUBSTRING(s1,1,s1_len-i+1) = SUBSTRING(s2,1,s2_len-i); END IF; END 

这在levenshtein距离的recursion描述中基本上是一个单独的步骤。 该函数返回1,如果距离最多为1,否则返回0。

由于这个函数不能完全计算levenshtein距离,所以速度要快得多。

你也可以修改这个函数,使得如果levenshtein距离至多是2或者3,通过自我recursion调用它返回true 。 如果MySQL不支持recursion调用,则可以复制该函数的稍微修改过的版本两次,然后调用它们。 但是你不应该使用recursion函数来计算精确的levenshtein距离。

我有一个专门的K距离search案例,在MySQL中安装了Damerau-Levenshtein UDF后,发现查询时间过长。 我想出了以下解决scheme:

  • 我有一个非常严格的search空间(9个字符的string限制为数值)。

用目标字段中每个字符位置的列创build一个新表(或将列附加到目标表)。 即。 我的VARCHAR(9)结束为9 TINYINT列+ 1 Id列匹配我的主表(为每列添加索引)。 我添加了触发器,以确保当我的主表更新时,这些新列总是得到更新。

要执行k距离查询,请使用以下谓词:

(Column1 = s [0])+(Column2 = s [1])+(Column3 = s [2])+(Column4 = s [3])+ …> = m

其中s是您的searchstring,m是匹配字符数(或者m = 9 – d,在我的情况下,d是我想返回的最大距离)。

经过testing,我发现超过一百万行的查询平均花费了4.6秒,在不到一秒的时间内就返回了匹配的id。 第二个查询返回我的主表中的匹配行的数据类似地花了一秒钟。 (将这两个查询合并为一个子查询或连接导致执行时间显着延长,我不知道为什么。)

虽然这不是Damerau-Levenshtein(不考虑替代),但对我来说就足够了。

虽然这个解决scheme可能不能很好地适应更大(长度)的search空间,但它对于这个限制性案例来说工作得非常好。

根据Chella的回答和Ryan Ginstrom的文章 ,模糊search可以这样实现:

 DELIMITER $$ CREATE FUNCTION fuzzy_substring( 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(0))), 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; SET j = 1; WHILE j <= s2_len DO SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10); IF c > c_temp THEN SET c = c_temp; END IF; SET j = j + 1; END WHILE; RETURN c; END$$ DELIMITER ;