2017-06-22 59 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

我剛剛重新產生了錯誤。不幸的是,這不足以在工作時間繼續進行。當我有空閒時間的時候更多地關注它。 –