2017-06-22 60 views
1

我发现一个非常奇怪的行为,比较mysql中的字符。mysql - 为什么认为右括号等于ö(o变音符)?

最简单的函数来再现是这样的:

set names utf8 collate utf8_general_ci; 
drop function if exists contains_bracket; 
delimiter ;; 
CREATE DEFINER=`db`@`%` FUNCTION `contains_bracket`(str varchar(255) CHARSET utf8) RETURNS varchar(255) CHARSET utf8 
    begin 
    declare i, result int; 
    declare letter varchar(1); 
    set result = 0; 
    set i = 1; 
    set str = lower(str); 
    while i <= length(str) do 
     set letter = substring(str, i, 1); 
     if letter = ']' then 
     set result = 1; 
     end if; 
     set i = i + 1; 
    end while; 
    return result; 
    end;; 
delimiter ; 

函数应该返回1,如果该参数包含一个右括号],否则为0。奇怪的是,在这个功能中,变音符ö被认为等于]

测试这样的:

select contains_bracket('[a]'), contains_bracket('abc'), contains_bracket('äöü'), contains_bracket('ö') 

会给

------------------------------- 
| '[a]' | 'abc' | 'äöü' | 'ö' | 
------------------------------- 
| 1 | 0 | 1 | 1 | 
------------------------------- 

这到底是怎么回事?有人可以解释吗?当使用utf8_general_ci时,']' = 'ö'是否为真,是否是mysql中的一个错误,或者是否有某些我错过的东西?

编辑:

连接字符集和整理是非常重要的,因为存储函数和过程保持字符集和校对他们创造了其一生中是活跃的。

请记住,在phpmyadmin中,数据交换是默认在utf8中。连接collat​​iom不会改变这一点。例如,当连接校对是latin1的,而我们在字符串中查询发送非ASCII字符,其价值将被损坏(例如,当我们输入'ä'(UTF8),服务器将看到_latin1'ä'

+0

您能否在第一次开始之前添加DETERMINISTIC并告诉我您是否仍然收到不正确的值? –

+0

你可能有一个原因,但为什么你在地球上循环,如果你可以使用mysql本地函数,即instr? –

+0

@krishKM我正在循环出于其他原因。这只是一个表现现象的功能。 –

回答

2

这真的确实看起来是字符集不匹配的问题。

请解决您的declare letter varchar(1);

应该declare letter varchar(1) CHARSET utf8;

这是怎么回事?

在这个作业

set letter = substring(str, i, 1);

substring结果作为letter被声明为与varchar(1)字符集默认处理latin1被转换到latin1

所以在比较if letter = ']'我们有ölatin1在左边和]在右边。

为什么他们被认为是平等的?

MySQL使用二进制表示来比较字符串。在v.5.6及以上版本中有一个功能WEIGHT_STRING()

该函数返回输入字符串的权重字符串。 返回值是一个二进制字符串,它表示字符串的排序值比较和 。

让我们来看看WEIGHT_STRING(letter)

set names utf8 collate utf8_general_ci; 
drop function if exists contains_bracket; 
delimiter ;; 
CREATE FUNCTION `contains_bracket`(str varchar(255) CHARSET utf8) RETURNS varchar(255) 
    begin 
    declare i int; 
    declare result varchar(255); 
    declare letter varchar(1); 
    set result = ''; 
    set i = 1; 
    set str = lower(str); 
    while i <= length(str) do 
     set letter = substring(str, i, 1); 
     if letter = ']' then 
     set result = concat(weight_string(letter), ' = ', letter); 
     set i = length(str); -- exit the loop 
     end if; 
     set i = i + 1; 
    end while; 
    return result; 
    end;; 
delimiter ; 

测试:

select contains_bracket('[a]'), contains_bracket('abc'), contains_bracket('äöü'), contains_bracket('ö'); 

会给

--------------------------------- 
| '[a]' | 'abc' | 'äöü' | 'ö' | 
--------------------------------- 
| ] = ] |  | ] = ö | ] = ö | 
--------------------------------- 

letter声明中修复了这个问题,使用正确的字符集。

而且更简单的方法来确定是否一个字符串包含另一个字符串:

select if(locate(']', '[a]'), 1, 0); -- returns 1 
select if(locate(']', 'äöü'), 1, 0); -- returns 0 
+0

我同意,它*看起来是一个不匹配字符集的问题,但它*看起来不像是与latin1和utf8之间的转换有关。 –

+0

谢谢,我已经延长了我的答案。 –

+0

我没有得到这些结果;你有什么'显示变量'%col%';'? –

1

这将是短了很多:

CREATE FUNCTION `contains_bracket`(str varchar(255) CHARSET utf8) 
     RETURNS varchar(255) CHARSET utf8 
    RETURN str LIKE '%]%'; 
    end;; 

为什么不能用呢?

OK,假定真正的任务不允许使用LIKE ...

有一个错误:使用CHAR_LENGTH(),不LENGTH()

好的,那不会改变我得到的结果。但我得到

mysql> select contains_bracket('[a]'), contains_bracket('abc'), contains_bracket('äöü'), contains_bracket('ö')\G 
*************************** 1. row *************************** 
    contains_bracket('[a]'): 1 
    contains_bracket('abc'): 0 
    contains_bracket('äöü'): 0 
    contains_bracket('ö'): 0 

所以,我不得不说“为我工作”。

也许在my.cnf中有一些其他的设置是不对的?你使用的是什么版本的MySQL?

嗯,我认为以下是真正的答案,因为我已经看到它在德国键盘上。键盘显示ö,但传输的代码是]。 (好像我是在上世纪80年代分配在斯图加特,只好用德语终端码C)

建议你做以下 - 辣椒SELECT HEX(...)存储的过程,看看你实际上是寻找]

+0

谢谢你的提示。我认为他们正在指出正确的方向。你说*适用于我*。我必须承认,在删除并重新创建几次函数后,我不再能够重现,但我无法用原始函数纠正生产数据库中的问题。我很困惑。 –

+0

正如我上面所述,使用相同语句创建的函数并不总是表现相同,具体取决于某些设置。如果可能的话,我会添加这些细节以使问题可以为每个人重复使用。我想我需要避开所述的奥秘,以便能够提供所有的细节。 –

+0

我刚刚重新产生了错误。不幸的是,这不足以在工作时间继续进行。当我有空闲时间的时候更多地关注它。 –