当您需要表示或输出在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
是同一个字符。
正则表达式
正则表达式除了对一般的不可见字符使用\
,为了做一些文本匹配,声明了模式,而这些模式中大量使用了()^%
等符号,例如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".
简单文本+正则表达式+SQL转义
有一段文字:010-12345678
,使用函数提取出最前面的010
步骤如下:
写出正则表达式
(\d+)-
。根据SQL的转义规则,对其中的
\
进行转义,同时在两边添加单引号,可以得到字符串'(\\d+)-'
。
在MaxCompute中执行如下命令进行验证:
select REGEXP_EXTRACT('010-12345678', '(\\d+)-');
--返回结果
010
JSON+正则表达式+SQL转义
如果字符串本身就含有转义字符,例如{"key":"this is very \"important\"."}
这段JSON,使用正则表达式匹配出其中的important
步骤如下:
写出正则表达式:
\"(.*)\"
。对其中的反斜线进行正则表达式的转义:
\\"(.*)\\"
。再对表达式进行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
原始的字符串进行反向操作,再结合原始字符串和正则表达式工具,可以推断出想要匹配的模式:
select '\\\\"(.*)\\\\"';
--返回结果
\\"(.*)\\"
SQL转义增强
SQL、JSON和正则表达式都使用了\
作为转义字符容易引起混乱,选择\
作为转义字符是因为在正常的语句中出现的频率很低,但是在几种规范组合的场景反而大量出现,出现转义字符膨胀的问题。为了处理好此类场景,MaxCompute引入了一种新的转义方式: R"()"
,在括号中书写的字符不再需要使用\
来进行转义。
例如:
R"(abc)"
等价于'abc'
。R'(\\"(.*)\\")'
等价于'\\\\"(.*)\\\\"'
。
这里的R
也可以使用小写字母r
,代表原始字符串(Raw String),双引号也可以替换为单引号。在MaxCompute中此方式对于想要转义的字符串不需要每个都添加\
,只需要在两边稍加修改即可。使用正则表达式匹配出JSON中important
的语句也可以修改为如下命令:
select REGEXP_EXTRACT(R'({"key":"this is very \"important\"."})', R'(\\"(.*)\\")');
--返回结果
important
此方式极大地简化了对JSON以及正则字符串的转义处理。