簡(jiǎn)介
在當(dāng)今這個(gè)互聯(lián)網(wǎng)和經(jīng)濟(jì)全球化時(shí)代,需要使用越來越多的應(yīng)用程序處理不同國(guó)家語言表示的數(shù)據(jù)。對(duì)開發(fā)人員而言,這就意味著在應(yīng)用程序開發(fā)的各個(gè)階段 — 數(shù)據(jù)庫設(shè)計(jì)、應(yīng)用程序設(shè)計(jì)和應(yīng)用程序編程 — 都要考慮到不同國(guó)家的語言需求。DB2 9 支持具有不同屬性的各種語言,比如重音符號(hào)(法語)、雙向(阿拉伯語)和大字符集(中文)。這些語言在存儲(chǔ)、處理、訪問和表示數(shù)據(jù)方面提出了各自不同的挑戰(zhàn)。受國(guó)家語言影響的數(shù)據(jù)不僅限于字符串?dāng)?shù)據(jù)。還包括有數(shù)值型、日期型和貨幣型數(shù)據(jù)。
字符與字符串?dāng)?shù)據(jù)的字節(jié)語義之間的對(duì)比
DB2 9 以前版本中的一些字符串函數(shù)從字節(jié)和雙字節(jié)單元的混合角度處理字符和圖形數(shù)據(jù)。正如之前解釋的那樣,越來越多的用戶根據(jù)不同國(guó)家語言的字符來考慮數(shù)據(jù)。DB2 9 的新功能解決了字符組成及其長(zhǎng)度計(jì)算方面的問題,本文將討論這些新功能。
對(duì)于單字節(jié)字符編碼模式,一個(gè)字節(jié)組成一個(gè)字符,單字節(jié)字符串的長(zhǎng)度與字符串的字節(jié)長(zhǎng)度相同。對(duì)于圖形字符串,兩個(gè)字節(jié)組成一個(gè)字符,使用雙字節(jié)數(shù)來表示字符串的長(zhǎng)度。但是對(duì)于多字節(jié)編碼,字符的字節(jié)長(zhǎng)度隨使用編碼模式的不同而不同,每個(gè)字符的長(zhǎng)度可能是一個(gè)字節(jié)或多個(gè)字節(jié)。本文中將使用字節(jié)計(jì)算字符串長(zhǎng)度的方法稱作字節(jié)語義,而使用字符數(shù)計(jì)算字符串長(zhǎng)度的方法稱作字符語義。
考慮以下的中文字符串:
圖 1. 中文字符串
如果使用字符語義計(jì)算字符串的長(zhǎng)度,則該字符串的長(zhǎng)度為 2。但是如果使用字節(jié)語義并使用 UTF-8 對(duì)字符進(jìn)行編碼,則該字符串的長(zhǎng)度為 6 字節(jié)。
對(duì)基于字符的函數(shù)的需求
SQL 中基于字符的數(shù)據(jù)在很多上下文中都與數(shù)值有關(guān),如下所述:
字符串變量的長(zhǎng)度:SUBSTR 函數(shù)的輸入?yún)?shù),決定了結(jié)果字符串的期望長(zhǎng)度或 LENGTH 函數(shù)的輸出。
字符串中的偏移量:LOCATE 函數(shù)的第二個(gè)參數(shù),指定了字符串中開始搜索的起始位置。
這些數(shù)值表示單字節(jié)數(shù)據(jù)的字節(jié)數(shù)和圖形或雙字節(jié)數(shù)據(jù)的雙字節(jié)數(shù)。但是對(duì)于多字節(jié)字符編碼(如 UTF-8),這些數(shù)值并不符合字符語義。下面的條件可以幫助我們理解為何需要基于字符的函數(shù)。
字符的組成
將字符看作一個(gè)單元而不是一個(gè)字節(jié)序列,這是進(jìn)行多字節(jié)字符的字符串操作的必要條件。應(yīng)用程序開發(fā)人員需要知道,分配緩沖區(qū)時(shí)應(yīng)該給每個(gè)字符分配多大內(nèi)存。因此,理解字符組成對(duì)編寫應(yīng)用程序處理多字節(jié)字符數(shù)據(jù)非常重要。 可以將字符定義為一個(gè)信息單元,對(duì)應(yīng)于書面語言的一個(gè)原子單元。每個(gè)字符由一個(gè)使用字符編碼的位序列表示。單個(gè)字符通常使用一個(gè)字節(jié)或多個(gè)字節(jié)進(jìn)行編碼,具體情況取決于使用的編碼方式。 考慮字符 “A” 和 “上面帶圈的大寫拉丁字母 A”。字符 “A” 的十六進(jìn)制表示是 x‘41’ 而 “上面帶圈的大寫拉丁字母 A” 的十六進(jìn)制表示是 x‘C385’。通過 SQL 函數(shù) hex() 可以獲取此表示。
圖 2. 字符的十六進(jìn)制表示
從上面的表示可以看到,顯示期間只存在一個(gè)字符。但是,“A” 的長(zhǎng)度是一個(gè)字節(jié)而 “上面帶圈的大寫拉丁字母 A” 的長(zhǎng)度則是兩個(gè)字節(jié)。
根據(jù)代碼單元計(jì)算的字符串長(zhǎng)度
字符字符串的長(zhǎng)度取決于用于編碼字符的字符編碼方式(ASCII、EBCDIC 和 Unicode)??梢允褂靡粋€(gè)或多個(gè)各自編碼的代碼單元來表示字符。因此,如果字符串中有相同的字符集,則其長(zhǎng)度可能隨使用編碼方式的不同而有所不同。 考慮一個(gè)字符例子 “音符 G 音譜號(hào)”。考慮表 1 中對(duì)此字符的不同編碼,您會(huì)發(fā)現(xiàn)不同代碼單元中的不同編碼的十六進(jìn)制表示及其長(zhǎng)度都有所不同。
表 1. 相同字符不同編碼的十六進(jìn)制表示
編碼 | UTF-8 | UTF-16(Big-Endian) | UTF-32(Big-Endian) |
十六進(jìn)制格式 | X'F09D849E' | X'D834DD1E' | X'0001D11E' |
各自代碼單元的長(zhǎng)度 | 4 | 2 | 1 |
從圖 3 可以看出如何獲取 “音符 G 音譜號(hào)” 字符按 UTF-8 編碼時(shí)的字節(jié)長(zhǎng)度。
圖 3. “音符 G 音譜號(hào)” 的字節(jié)長(zhǎng)度
搜索字符
在字符串中搜索特定的子字符串時(shí),首先執(zhí)行搜索,然后返回的結(jié)果(字符串中的位置)為字節(jié)位置數(shù),而不是正確的字符或代碼單元的位置。圖 4 展示了對(duì) “a” 的搜索,“a” 的實(shí)際字符位置是 2,而輸出的位置是 3,原因在于字符串中有多字節(jié)字符。
圖 4. 字符串中的搜索結(jié)果
字符分解
將多字節(jié)字符數(shù)據(jù)看作字節(jié)序列可能導(dǎo)致字符串函數(shù)執(zhí)行意外的字符分解。在圖 5 中,已經(jīng)指定字符串第一個(gè)字節(jié)的長(zhǎng)度為 1 的子字符串。由于第一個(gè)字符是多字節(jié)的,因此會(huì)導(dǎo)致字符分解和錯(cuò)誤輸出。
圖 5. SUBSTR 函數(shù)分解字符
指定起始位置
可能需要為 LOCATE 之類的函數(shù)提供輸入以指定搜索的起始位置。對(duì)于多字節(jié)數(shù)據(jù)可能會(huì)存在一些問題,可能得不到預(yù)期的結(jié)果。在圖 6 中,搜索第三個(gè)字節(jié)后的字符,如果所有的字符都是單字節(jié)字符的話應(yīng)該搜索到第二次出現(xiàn)的 “a” 字符。但是由于第一個(gè)字符是多字節(jié)字符,因此得到結(jié)果 3,它是搜索字符串的第一次出現(xiàn)的位置。
圖 6. 使用 LOCATE 指定起始位置
基于字符的函數(shù)
除了 DB2 早期版本中使用字節(jié)語義處理字符數(shù)據(jù)的字符串函數(shù)之外,DB2 9 還引入了一組理解字符語義的基于字符的字符串函數(shù)。如果采用特殊編碼的某個(gè)字符的長(zhǎng)度跨越了多個(gè)字節(jié),則基于字符的字符串函數(shù)可以將每個(gè)字符處理為一個(gè)單元而不是一個(gè)字節(jié)序列。
引入字符串長(zhǎng)度單元
DB2 的基于字符的字符串函數(shù)引入了字符串長(zhǎng)度單元的概念來理解字符編碼,根據(jù)該概念,考慮使用輸入字符串來進(jìn)行字符串操作。DB2 9 for Linux, UNIX, and Windows 的字符串單元分別為 OCTETS、CODEUNITS16 和 CODEUNITS32。
字符串函數(shù)擁有數(shù)值規(guī)范,或者說結(jié)果是輸入數(shù)據(jù)相關(guān)的數(shù)值。字符串長(zhǎng)度單元屬于數(shù)值。待執(zhí)行的字符串操作可能導(dǎo)致不同的輸出,取決于計(jì)算字符所使用的字符串長(zhǎng)度單元。一些函數(shù)的輸入是數(shù)值,比如字符串函數(shù)的起始、長(zhǎng)度和偏移量參數(shù)。而其他一些函數(shù)的返回結(jié)果是數(shù)值,比如搜索字符串中指定的子字符串的出現(xiàn)位置,首先執(zhí)行搜索然后結(jié)果返回為字符串長(zhǎng)度單元中隱式或顯式指定的數(shù)字。
使用 OCTETS 作為字符串長(zhǎng)度單元時(shí),通過簡(jiǎn)單地計(jì)算字符串的字節(jié)數(shù)即可確定字符串的長(zhǎng)度。CODEUNITS16 指定將 Unicode UTF-16 用于字符語義。同樣,CODEUNITS32 指定使用 Unicode UTF-32 來理解多字節(jié)字符的字符邊界。
使用 CODEUNITS16 或 CODEUNITS32 計(jì)算代碼單元得到的結(jié)果是相同的,除非使用了增補(bǔ)字符和代理對(duì)。使用增補(bǔ)字符時(shí),對(duì)于一個(gè)增補(bǔ)字符,使用 CODEUNITS16 計(jì)算是兩個(gè) UTF-16 代碼單元,而使用 CODEUNITS32 計(jì)算則是一個(gè) UTF-32 代碼單元。
如果使用 CODEUNITS 來獲取字符的長(zhǎng)度,則用作字符串函數(shù)輸入的 CODEUNITS 的不同會(huì)導(dǎo)致輸出的不同。
清單 1. 使用不同的 CODEUNITS 所得到的字符串長(zhǎng)度
VALUES CHARACTER_LENGTH(X'F09D849E', OCTETS)
1
-----------
4
1 record(s) selected.
VALUES CHARACTER_LENGTH(X'F09D849E', CODEUNITS16)
1
-----------
2
1 record(s) selected.
VALUES CHARACTER_LENGTH(X'F09D849E', CODEUNITS32)
1
-----------
1
1 record(s) selected.
DB2 9 中基于字符的字符串函數(shù)
CHARACTER_LENGTH
如 SQL 標(biāo)準(zhǔn)中所述,此函數(shù)使用字符語義查找字符字符串的長(zhǎng)度。此函數(shù)與 DB2 中的 LENGTH 函數(shù)類似,并擁有一個(gè)可選的字符串長(zhǎng)度單元,可以用來表示結(jié)果。與 LENGTH 函數(shù)不同,CHARACTER_LENGTH 只接受基于字符串的輸入數(shù)據(jù)。該函數(shù)包含兩個(gè)參數(shù),第一個(gè)參數(shù)是字符串,而第二個(gè)參數(shù)是代碼單元。在很多情形下,您需要根據(jù)代碼單元計(jì)算的字符串長(zhǎng)度,可以使用基于字符的函數(shù)來獲取根據(jù)字符串單元計(jì)算的字符串長(zhǎng)度。
考慮之前討論的 “音符 G 音譜號(hào)” 字符例子。
清單 2. 使用 CHARACTER_LENGTH 獲取基于 CODEUNITS 的字符串長(zhǎng)度
VALUES CHAR_LENGTH(X'F09D849E',CODEUNITS16)
1
-----------
2
1 record(s) selected.
VALUES CHAR_LENGTH(X'F09D849E',CODEUNITS32)
1
-----------
1
1 record(s) selected.
使用基于字符的字符串函數(shù)可以解決獲取基于 CODEUNITS 的字符串長(zhǎng)度時(shí)的問題。
OCTET_LENGTH
如 SQL 標(biāo)準(zhǔn)中所述,此函數(shù)返回輸入字符串的八位字節(jié)長(zhǎng)度或字節(jié)長(zhǎng)度。它與對(duì)單字節(jié)數(shù)據(jù)類型使用 LENGTH 函數(shù)類似。如果使用雙字節(jié)數(shù)據(jù)類型作為輸入,它就會(huì)給出雙倍的 LENGTH 函數(shù)值。使用 CHARACTER_LENGTH 并使用 OCTETS 作為字符串長(zhǎng)度單元時(shí)也會(huì)產(chǎn)生同樣的功能。
清單 3. 使用 OCTECT_LENGTH 獲取字符串的字節(jié)長(zhǎng)度
VALUES OCTET_LENGTH(X'F09D849E')
1
-----------
4
1 record(s) selected.
LOCATE
LOCATE 函數(shù)返回一個(gè)字符串在另一個(gè)字符串中第一次出現(xiàn)的起始位置。如果沒有找到搜索字符串,并且參數(shù)都不為空,則所得的結(jié)果是零。如果找到搜索字符串,則所得結(jié)果是一個(gè)從 1 到源字符串實(shí)際長(zhǎng)度之間的一個(gè)數(shù)字。如果指定了可選的起始位置,則表明它是源字符串中開始進(jìn)行搜索的字符位置??梢灾付ㄒ粋€(gè)可選的字符串長(zhǎng)度單元來指明在哪些單元中表示函數(shù)的起始位置和結(jié)果。
可以使用基于字符的函數(shù)來解決在 LOCATE 函數(shù)中指定起始位置的問題,如圖 7 所示:
圖 7. 通過 CODEUNITS 使用 LOCATE
POSITION
POSITION 函數(shù)返回一個(gè)字符串在另一個(gè)字符串中第一次出現(xiàn)的起始位置。如果沒有找到搜索字符串,并且參數(shù)都不為空,則所得的結(jié)果是 0。如果找到搜索字符串,則所得結(jié)果是一個(gè)從 1 到輸入字符串實(shí)際長(zhǎng)度之間的一個(gè)數(shù)字,使用顯式指定的代碼單元來表示。POSITION 函數(shù)是在 SQL 標(biāo)準(zhǔn)中進(jìn)行定義的。它與 DB2 家族之間實(shí)現(xiàn)的 POSSTR 函數(shù)相似但不相同。
使用基于字符的函數(shù)可以解決將字節(jié)位置返回為字符位置的問題。圖 8 展示了如何使用 LOCATE 函數(shù)來實(shí)現(xiàn)此目的。
圖 8. 通過 CODEUNITS 使用 POSITION
SUBSTRING
SUBSTRING 函數(shù)返回字符串的子字符串。子字符串是輸入字符串的零個(gè)或多個(gè)相鄰字符串長(zhǎng)度單元。除了輸入字符串之外,SUBSTRING 函數(shù)還有其他三個(gè)參數(shù),它們分別是:起始位置、長(zhǎng)度和代碼單元指定。起始位置指定了輸入字符串中結(jié)果的第一個(gè)字符串長(zhǎng)度單元所在的位置。長(zhǎng)度參數(shù)指定了所需子字符串的長(zhǎng)度。使用基于字符的函數(shù)時(shí)不會(huì)發(fā)生用于構(gòu)建字符的 CODEUNITS 分解。圖 9 展示了如何防止多字節(jié)字符的分解。
圖 9. 通過 CODEUNITS 使用 SUBSTRING
處理不正確的數(shù)據(jù)或不完整的數(shù)據(jù)
涉及多字節(jié)字符的字符串操作可能遇到字符不正確(編碼中沒有定義字節(jié)合并)或字符不完整(擁有多字節(jié)字符的部分字節(jié))的情形??紤]在使用新的基于字符的字符串函數(shù)執(zhí)行字符串操作時(shí)可能導(dǎo)致這一狀況的常見情形。字符 “音符 G 音譜號(hào)”(UTF-8 十六進(jìn)制格式為 X‘F09D849E’)就是這樣的例子,使用 CODEUNITS16 時(shí)其長(zhǎng)度為 2。
輸入字符串的問題
不完整的字符串?dāng)?shù)據(jù)
可以將擁有部分字符的字符串?dāng)?shù)據(jù)稱為不完整的字符串?dāng)?shù)據(jù)。假設(shè)您擁有一個(gè) UTF-8 編碼的字符,其長(zhǎng)度為 3 字節(jié),而字符串只擁有編碼的前兩個(gè)字節(jié)。如果您使用 CODEUNITS16 來計(jì)算前兩個(gè)字節(jié)的長(zhǎng)度,則函數(shù)將給出一個(gè)警告。
清單 4. 使用不完整的輸入字符串?dāng)?shù)據(jù)
VALUES CHARACTER_LENGTH(X'849E',CODEUNITS16)
1
-----------
2
SQL1289W During conversion of an argument to "SYSIBM.CHARACTER_LENGTH"
from code page "1208" to code page "1200", one or more invalid characters were
replaced with a substitute character, or a trailing partial multi-byte character was
omitted from the result. SQLSTATE=01517
1 record(s) selected with 1 warning messages printed.
不正確的字符串?dāng)?shù)據(jù)
每種字符編碼都具有針對(duì)特殊字符的字節(jié)集或字節(jié)組合集。字符串函數(shù)的輸入字符串?dāng)?shù)據(jù)可能擁有源字符串中的一些錯(cuò)誤字符或無效字符。如果 DB2 在執(zhí)行 CODEUNITS16 或 CODEUNITS32 計(jì)算時(shí)遇到無效字符,則它在字節(jié)序列形成部分函數(shù)結(jié)果時(shí)用替代字符替換任何此類的字節(jié)序列。十六進(jìn)制格式的 X‘80’ 用 UTF-8 編碼是無效的,遇到它時(shí)會(huì)拋出警告。
清單 5. 使用不完整的字符數(shù)據(jù)
VALUES CHARACTER_LENGTH(X'80',CODEUNITS16)
1
-----------
1
SQL1289W During conversion of an argument to "SYSIBM.CHARACTER_LENGTH"
from code page "1208" to code page "1200", one or more invalid characters were
replaced with a substitute character, or a trailing partial multi-byte character
was omitted from the result. SQLSTATE=01517
1 record(s) selected with 1 warning messages printed.
OCTETS 和 圖形字符串輸入
在 SUBSTRING FUNCTION 中,指定了 OCTETS 并且函數(shù)的輸入是圖形數(shù)據(jù),而 <start> 參數(shù)不是奇數(shù)或 <length> 參數(shù)不是偶數(shù),則會(huì)導(dǎo)致類似將圖形字符分解為兩個(gè)字節(jié)那樣的錯(cuò)誤。
清單 6. 字符分解 VALUES SUBSTRING(GRAPHIC('K'),2,1,OCTETS)
1
--
SQL20289N Invalid string length unit "OCTETS" in effect for function
"SYSIBM.SUBSTRING". SQLSTATE=428GC
輸出字符串的問題
獨(dú)立代理或不完整的字符串?dāng)?shù)據(jù)
當(dāng)使用兩個(gè) 16 位代碼單元序列表示字符時(shí),將該字符稱為代理對(duì)。代理對(duì)可以區(qū)分為高代理和低代理。在字符串函數(shù)中使用 CODEUNITS16 時(shí),DB2 進(jìn)行單一代碼單元或獨(dú)立代碼單元的區(qū)分。即,如果您擁有一個(gè)代理對(duì),則使用 CODEUNITS16 的字符長(zhǎng)度為 2,而使用 CODEUNITS32 的字符長(zhǎng)度為 1。因此如 SUBSTRING 之類的函數(shù)可以根據(jù)您提供的參數(shù)分解代理對(duì)。
替換字符插入導(dǎo)致緩沖區(qū)溢出
插入替換字符時(shí),字符串的字節(jié)長(zhǎng)度可能增加。如果長(zhǎng)度增加超出了輸出所能使用的緩沖區(qū)空間,則字符串的尾部將被截?cái)啵覍⑹盏揭粋€(gè)警告:將字符串指派給另一個(gè)長(zhǎng)度較短的字符串?dāng)?shù)據(jù)類型時(shí)字符串的值將被截?cái)唷?/p>
向后兼容
注意:新的字符串函數(shù)位于 SYSIBM 函數(shù)路徑下,而舊一些的函數(shù)位于 SYSFUN 路徑下。希望您使用新的 SYSIBM 函數(shù)路徑,既便您沒有使用字符串單元參數(shù)也是如此。默認(rèn)情況下,在默認(rèn)的 CURRENT PATH 中 SYSIBM 函數(shù)路徑位于 SYSFUN 之前。所有的舊函數(shù)仍然受到支持。
性能考慮
基于字符的函數(shù)可能需要將輸入數(shù)據(jù)字符串轉(zhuǎn)換為一個(gè)中間的 UNICODE 代碼頁,比如 UTF-16 或 UTF-32,然后才能對(duì)它進(jìn)行處理。如果結(jié)果數(shù)據(jù)是一個(gè)字符串,那么中間結(jié)果也要轉(zhuǎn)換回輸入代碼頁。OCTETS 作為一種字符串單元指定不需要任何轉(zhuǎn)換,而且使用起來更加有效。CODEUNITS16 和 CODEUNIST32 作為字符串單元可能導(dǎo)致代碼頁轉(zhuǎn)換。雖然 DB2 執(zhí)行自我優(yōu)化,但是是否需要代碼頁轉(zhuǎn)換還不清楚。轉(zhuǎn)換成本對(duì) LOB 輸入更加重要,因?yàn)檩斎胱址拇笮】赡芎艽蟆?/p>
結(jié)束語
本文向您簡(jiǎn)要地介紹了 DB2 數(shù)據(jù)服務(wù)器中新增的基于字符的字符串函數(shù)。首先介紹了一些關(guān)鍵概念,如針對(duì)字符串?dāng)?shù)據(jù)的字符語義和字節(jié)語義。接著討論了需要使用這些函數(shù)的原因,并舉例說明了一些常見的場(chǎng)景。還了解了代碼單元和基于字符的函數(shù)的概念。然后解釋了這些函數(shù)如何幫助您解決之前討論的問題,并對(duì)每個(gè)場(chǎng)景進(jìn)行舉例說明。最后,討論了使用這些函數(shù)時(shí)的常見問題和性能考慮。理想情況下,應(yīng)該使用這些函數(shù)更好地執(zhí)行字符串操作,將更多的應(yīng)用程序邏輯植入 SQL 層而不是在應(yīng)用程序中實(shí)現(xiàn)這些邏輯。
聯(lián)系客服