當您需要表示或輸出在MaxCompute中有特殊意義或者無法直接輸入的字元時,此時需要對字元進行轉義,以確保字串資料的正確表示和處理。MaxCompute逸出字元表達字串中的特殊字元或將其後跟的字元解釋為其本身,本文為您介紹MaxCompute中逸出字元的使用情境和使用樣本。
逸出字元使用情境
在編程領域幾乎所有的字串表示都會遇到逸出字元的問題,而解決思路也都是類似的:
首先制定一個規則,規定一些有特殊含義的字元,例如
'
、"
。然後對這些特殊含義的字元以及不可見字元做特殊處理,例如
\'
、\"
。最後針對第2步中的方式再做個補充,做一些特殊處理,例如
\\
表示反斜線。
有很多規範使用\
進行轉義,常見使用情境如下:
有些規範並沒有使用\
,例如URL中使用%
進行轉義,XML中使用&
進行轉義。
SQL(MaxCompute)
在SQL文法中,字串的內容需要寫在半形單引號('')或者雙引號("")中,例如"abc"
、'123'
。對於一些字串中本身就有單引號或者雙引號的情況,如果只包括一種引號,則可以簡單使用另一種引號,例如 '雙引號(")'
。但是如果一段文字中既包括半形單引號又包括半形雙引號,例如如下語句:
雙引號(")
單引號(')
不僅包含了''
和""
,還包含了分行符號,對於這種,規定使用平時較少用到的反斜線(\
)作為特殊字元,表示和後面的字元共同表示一個字元,例如\n
表示換行,\"
、\'
表示字串中的引號,並不代表字串結束,則上面的內容可以寫成一個字串:'雙引號(")\n單引號(\')'
或者 "雙引號(\")\n單引號(')"
。
但是這樣反斜線又有了特殊的含義,對於字串中的反斜線,就要寫成\\
,第一個表示轉義,第二個表示真正的字元。SQL中常見的逸出字元寫法如下:
逸出字元寫法 | 說明 |
\b | 退格(backspace),將當前位置移到前一列。 |
\t | 水平製表(tab)。 |
\n | 換行(newline),將當前位置移到下一行開頭。 |
\r | 斷行符號(carriage-return),將當前位置移到本行開頭。 |
\' | 單引號。 |
\" | 雙引號。 |
\\ | 反斜線。 |
\; | 分號。 |
\Z | control-Z。 |
\0或\00 | 結束符。 |
反斜線後面跟了一個不需要轉義的字元,這種情況和沒有逸出字元相同,例如 \a
和 a
是同一個字元。
Regex
Regex除了對一般的不可見字元使用\
,為了做一些文本匹配,聲明了模式,而這些模式中大量使用了()^%
等符號,例如MaxCompute的底層正則引擎使用了RE2,RE2詳情請參見RE2,一些使用\
的轉義寫法如下:
逸出字元寫法 | 說明 |
\d | 匹配任何數字,等價於 |
\D | 匹配任何非數字,等價於 |
\s | 匹配任何空白符,等價於 |
\S | 匹配任何非空白符,等價於 |
\w | 匹配任何數字字母字元,等價於 |
\W | 匹配任何非數字字母字元,等價於 |
JSON
JSON是一種常用的傳輸資料的文本協議,標準比較簡單,完整的標準請參見JSON,使用\
的轉義寫法如下:
逸出字元寫法 | 說明 |
\" | 半形雙引號。 |
\\ | 反斜線。 |
\/ | 正斜線。 |
\b | 退格符。 |
\f | 換頁符。 |
\n | 分行符號。 |
\r | 斷行符號符。 |
\t | 水平定位字元。 |
\u+4位16進位 | unicode。 |
可以看到JSON許多逸出字元的設計和SQL非常相似,您可以使用JSON線上工具JSONLint驗證一段文本是否符合JSON規範。
逸出字元使用樣本
在逸出字元使用的過程中,由於許多規範都使用了\
做轉義,在組合使用的時候,較難理解。這時候需要理解文本的嵌套結構,輔助使用上文提到的線上工具,逐層進行轉義和反轉義,便可寫出正確語義的代碼語句,樣本如下:
逸出字元後字元解釋為其本身
MaxCompute SQL中的字串常量可以用單引號或雙引號表示。您可以在單引號括起的字串中包含雙引號,或在雙引號括起的字串中包含單引號,否則要用逸出字元來表達。表達方式如下:
"I'm a happy manong." 'I\'m a happy manong.'
在LIKE字元匹配中,要匹配
%
或_
本身,則要對其進行轉義:select 'ab_cde' like 'ab\_c%'; --返回結果 true
特殊字元
'a\tb'
字串裡有三個字元,\t
被視為一個字元。select length('a\tb'); --返回結果 3
'a\ab'
字串裡有三個字元,\a
被解釋為普通的a
。select 'a\ab',length('a\ab'); --返回結果 aab,3
JSON+SQL轉義
有一段JSON:{"key":"this is very \"important\"."}
,想要使用get_json_object
函數提取Value值,卻得不到正確的結果:
-- 使用新版本的get_json_object,會檢查json的完整性
set odps.sql.udf.getjsonobj.new=true;
select get_json_object('{"key":"this is very \"important\"."}', '$.key');
--返回結果
NULL
原因出現在這段JSON文本只是簡單地在兩側加了半形單引號,裡面的內容並沒有經過SQL的轉義,直接select
文本驗證此結論:
select '{"key":"this is very \"important\"."}';
--返回結果
{"key":"this is very "important"."}
可以看到反斜線消失了,原因是編譯器解析的時候把JSON中的逸出字元當做了SQL的逸出字元,\"
被解釋成了 "
,導致得到的結果並不符合JSON文法,從而無法解析。對JSON文本加上正確的SQL逸出字元:'{"key":"this is very \\"important\\"."}'
,再次使用get_json_object
函數提取Value值,得到正確的結果:
set odps.sql.udf.getjsonobj.new=true;
select get_json_object('{"key":"this is very \\"important\\"."}', '$.key');
--返回結果
this is very "important".
簡單文本+Regex+SQL轉義
有一段文字:010-12345678
,使用函數提取出最前面的010
步驟如下:
寫出Regex
(\d+)-
。根據SQL的轉義規則,對其中的
\
進行轉義,同時在兩邊添加單引號,可以得到字串'(\\d+)-'
。
在MaxCompute中執行如下命令進行驗證:
select REGEXP_EXTRACT('010-12345678', '(\\d+)-');
--返回結果
010
JSON+Regex+SQL轉義
如果字串本身就含有逸出字元,例如{"key":"this is very \"important\"."}
這段JSON,使用Regex匹配出其中的important
步驟如下:
寫出Regex:
\"(.*)\"
。對其中的反斜線進行Regex的轉義:
\\"(.*)\\"
。再對錶達式進行SQL的轉義,將所有的
\
替換成\\
,為了簡便,兩邊再添加單引號,得到字串:'\\\\"(.*)\\\\"'
。
在MaxCompute中執行如下命令進行驗證:
select REGEXP_EXTRACT('{"key":"this is very \\"important\\"."}', '\\\\"(.*)\\\\"');
--返回結果
important
結果符合預期,類似此情境建議先對JSON進行解析,邏輯會更清晰,命令如下:
set odps.sql.udf.getjsonobj.new=true;
select REGEXP_EXTRACT(get_json_object('{"key":"this is very \\"important\\"."}', '$.key'), '"(.*)"');
--返回結果
important
通過以上樣本,可以正向地寫出符合業務需求的語句,但是這麼多轉義之後,看到的字串往往非常難懂,'\\\\"(.*)\\\\"'
究竟想要匹配什麼,已經不太容易理解和維護了。可以通過select
原始的字串進行反向操作,再結合原始字串和Regex工具,可以推斷出想要匹配的模式:
select '\\\\"(.*)\\\\"';
--返回結果
\\"(.*)\\"
SQL轉義增強
SQL、JSON和Regex都使用了\
作為逸出字元容易引起混亂,選擇\
作為逸出字元是因為在正常的語句中出現的頻率很低,但是在幾種規範組合的情境反而大量出現,出現逸出字元膨脹的問題。為了處理好此類情境,MaxCompute引入了一種新的轉義方式: R"()"
,在括弧中書寫的字元不再需要使用\
來進行轉義。
例如:
R"(abc)"
等價於'abc'
。R'(\\"(.*)\\")'
等價於'\\\\"(.*)\\\\"'
。
這裡的R
也可以使用小寫字母r
,代表原始字串(Raw String),雙引號也可以替換為單引號。在MaxCompute中此方式對於想要轉義的字串不需要每個都添加\
,只需要在兩邊稍加修改即可。使用Regex匹配出JSON中important
的語句也可以修改為如下命令:
select REGEXP_EXTRACT(R'({"key":"this is very \"important\"."})', R'(\\"(.*)\\")');
--返回結果
important
此方式極大地簡化了對JSON以及正則字串的轉義處理。