HiveQL和SparkSQL中的正则
1. 正则操作符
- hive
hive中的正则查找属于关系操作符,rlike
和regexp
二者用法完全相同。
css
a rlike 正则表达式
a regexp 正则表达式
- spark
spark中正则查找即可以写成关系操作符又可以写成函数形式,在3.2.0之前只有rlike
,并且3.2.0之前的文档中写法是str rlike regexp
,从3.2.0开始的文档中写成了rlike(str, regexp)
形式,但是从实际测试情况看3.2.0之前的版本中也可以写成rlike(str, regexp)
形式。
同时spark中支持 str not rlike regexp
的用法。
从3.2.0开始增加了regexp
用法,格式regexp(str, regexp)
。同时增加类似于presto中的regexp_like
函数,格式regexp_like(str, regexp)
。
2. 正则提取
通过 regexp_extract(string subject, string pattern, int index)
UDF完成。
- 函数:
regexp_extract(string subject, string pattern, int index)
- 返回结果:string
- 使用说明:
- param1:要匹配的字符串(非字符串也可以)或字段
- param2:正则表达式
- param3:是 Java regex Matcher group() 方法索引,表示要返回的部分。0表示把整个正则表达式对应的结果全部返回。
例如,regexp_extract('foothebar', 'foo(.*?)(bar)', 2) 返回 'bar'
。
请注意,在使用预定义的字符类时需要进行转义,同java中的使用方式 :使用 '\s'
将匹配字母 s
; '\\s'
将会匹配任何空白字符。
官方示例中的执行结果
sql
-- 其中param2表示返回正则表达式中$2的结果,即(bar)对应的结果
> select regexp_extract('foothebar', 'foo(.*?)(bar)', 2);
+------+
| _c0 |
+------+
| bar |
+------+
-- 如果param2值为0,则返回pattern匹配的整个结果
> select regexp_extract('foothebar', 'foo(.*?)(bar)', 0);
+------------+
| _c0 |
+------------+
| foothebar |
+------------+
param1并非只可以是string类型,如下示例中param1的type为int
sql
> select regexp_extract(5454310223, '10', 0);
+------+
| _c0 |
+------+
| 10 |
+------+
如果param2的pattern中不包含(),则param3只可以是0,其他值会报错
sql
> select regexp_extract('100.034万人民币', '^\\d+\\.*\\d+', 0) as result;
+----------+
| result |
+----------+
| 100.034 |
+----------+
> select regexp_extract('100.034万人民币', '^\\d+\\.*\\d+', 1) as result;
Error: Error while compiling statement: FAILED: SemanticException [Error 10014]: Line 1:7 Wrong arguments '1': org.apache.hadoop.hive.ql.metadata.HiveException: Unable to execute method public java.lang.String org.apache.hadoop.hive.ql.udf.UDFRegExpExtract.evaluate(java.lang.String,java.lang.String,java.lang.Integer) with arguments {100.034万人民币,^\d+\.*\d+,1}:No group 1 (state=42000,code=10014)
3. 转义的使用
通过\
对特殊符号进行转义,在正则表达式中如何正确使用\
?
需要使用转义的情形。
情形一:正则表达式中表示特殊含义的符号,如表示匹配任意字符的.
,还有?
、+
等。
对于在正则中有特殊含义的符号来说,如果想要正则匹配符号本身,则需要在标点符号前面加上\
进行转义,如\.
匹配符号.
,\\
匹配符号\
,\?
匹配符号?
等等
情形二:正则表达式中预定义了很多带有\
的模式,如表示匹配任意数字的\d
模式,这里的\
是并不表示转义,而是作为特殊符号与d组成一个整体。同类的还有\w
匹配字母数字及下划线,\s
匹配任意空白字符,\t
匹配tab符号等等
在代码中无法直接使用\d
来匹配任意数字,因为在代码中单独出现\
时表示转义含义,所以代码中单独出现\d
时并不表示\d
本身,而是表示的是转义符d
,d被转义后仍然是d,所以直接只用时匹配的是字母d而不是任意数字。
正确使用:需要再\
再加一个\
,将\d
中的\
转移成反斜线本身,即\\d
。
结论
对于这种本身就带有\
符号的模式来说,如果直接使用,则并不是模式本身的含义。在使用时需要在\
符号前面再加一个\
。 如使用\d
时,则需要写成\\d
才表示\d
模型本身的含义;
如果模式本身是不带有\
符号的,则在需要转义时直接在前面加一个\
就行 。 如需要转义"
符号直接写成\"
即可(转义符"
符号无特殊含义,所以\"
还是"
本身)。
转义的终极目的是在最终在使用模式的地方保证出现的是模式本身的符号。
注意,在hive、spark或presto中正则匹配时是区分大小写的。
sql
-- 以下结果为true
select rlike(lower('Nespresso 胶囊咖啡 瑞士进口 意式浓缩黑咖啡胶囊 Nespresso咖啡机适用 Capriccio 10颗装'),'.*capriccio.*')
-- 以下结果为false
select rlike('Nespresso 胶囊咖啡 瑞士进口 意式浓缩黑咖啡胶囊 Nespresso咖啡机适用 Capriccio 10颗装','.*capriccio.*')
3.1. 验证
- 示例1:
sql
> select regexp_extract('wab23cd','(\d)',1);
+------+
| _c0 |
+------+
| d |
+------+
> select regexp_extract('wab23cd','(\\d)',1);
+------+
| _c0 |
+------+
| 2 |
+------+
> select regexp_extract('wab23cd','(\a)',1);
+------+
| _c0 |
+------+
| a |
+------+
> select regexp_extract('wab23cd','(\\a)',1);
+------+
| _c0 |
+------+
| |
+------+
结果解释:
- 第一个SQL结果返回了字母d,并不是数字2,这是因为直接在代码中写
\d
所表达的含义是转义符+d
,而不是反斜线+d
,转义符+d的结果仍然是d,所以在正则匹配时匹配到了d。 - 第二个SQL结果返回了数字2,这是因为
\\d
中,第一个\
表示转义符,第二个\
表示反斜线,第一个将第二个转义成了反斜线字面意思,所以\\d
表示达的含义是反斜线+d
,是表示匹配数字。 - 第三个SQL结果返回了字母a,这是因为直接在代码中写
\a
所表达的含义是转义符+a
,转义符+a的结果仍然是a,所以在SQL结果匹配到了a。 - 第四个SQL结果返回了空,这是因为代码中写的
\\a
在正则中表示反斜线+a
,而反斜线+a在正则中无其他含义,因此SQL结果无匹配结果。
对于\t
则稍有不同,参见示例如下
sql
> select 'w\tab23d';
+----------+
| _c0 |
+----------+
| w ab23d |
+----------+
> select regexp_extract('w\tab23d','(w\ta)',1);
+------+
| _c0 |
+------+
| w a |
+------+
> select regexp_extract('w\tab23d','(w\\ta)',1);
+------+
| _c0 |
+------+
| w a |
+------+
注意第二个SQL和第三个SQL结果相同,第二个SQL中\t
表示转义符+t
,转义符+t
结果表示tab符号,则结果返回字符串中的制表符。第三个SQL中\\t
,第一个\
表示转义符,第二个\
表示反斜线,第一个将第二个转义成了反斜线字面意思,所以\\t
表示达的含义是反斜线+t
,在模式中表示匹配制表符。
- 示例2
sql
> select regexp_extract('wab23?cd','(?)',1);
Error: Error while compiling statement: FAILED: SemanticException [Error 10014]: Line 1:7 Wrong arguments '1': org.apache.hadoop.hive.ql.metadata.HiveException: Unable to execute method public java.lang.String org.apache.hadoop.hive.ql.udf.UDFRegExpExtract.evaluate(java.lang.String,java.lang.String,java.lang.Integer) with arguments {wab23?cd,(?),1}:No group 1 (state=42000,code=10014)
> select regexp_extract('wab23?cd','(\?)',1);
Error: Error while compiling statement: FAILED: SemanticException [Error 10014]: Line 1:7 Wrong arguments '1': org.apache.hadoop.hive.ql.metadata.HiveException: Unable to execute method public java.lang.String org.apache.hadoop.hive.ql.udf.UDFRegExpExtract.evaluate(java.lang.String,java.lang.String,java.lang.Integer) with arguments {wab23?cd,(?),1}:No group 1 (state=42000,code=10014)
> select regexp_extract('wab23?cd','(\\?)',1);
+------+
| _c0 |
+------+
| ? |
+------+
结果解释:
第一个SQL和第二个SQL都发生了报错,在正则表达式中?
具有特殊含义,并且不会单独出现,所以SQL报错,第二个SQL写成\?
表示转义符+?
,而?
被转义后还是?
,所以和第一个SQL和第二个SQL报相同的错误。
在正则匹配中想要匹配?
,本身需要的是\?
(反斜线+?),第三个sql中,第一个反\
被当成转义符将第二个\
转义成反斜线字面含义,因此\\?
表达的是反斜线+?
的含义,从而实现正则匹配问号。
- 示例3
sql
> select regexp_extract('wab23?c.d','(.)',1);
+------+
| _c0 |
+------+
| w |
+------+
> select regexp_extract('wab23?c.d','(\.)',1);
+------+
| _c0 |
+------+
| w |
+------+
> select regexp_extract('wab23?c.d','(\\.)',1);
+------+
| _c0 |
+------+
| . |
+------+
结果解释同示例2。
- 示例4
sql
> select 'w ab\23?c.d';
+-------------+
| _c0 |
+-------------+
| w ab23?c.d |
+-------------+
> select 'w ab\\23?c.d';
+--------------+
| _c0 |
+--------------+
| w ab\23?c.d |
+--------------+
> select regexp_extract('w ab\\23?c.d','(\)',1);
+------+
| _c0 |
+------+
| |
+------+
> select regexp_extract('w ab\\23?c.d','(\\)',1);
Error: Error while compiling statement: FAILED: SemanticException Line 1:7 Wrong arguments '1': org.apache.hadoop.hive.ql.metadata.HiveException: Unable to execute method public java.lang.String org.apache.hadoop.hive.ql.udf.UDFRegExpExtract.evaluate(java.lang.String,java.lang.String,java.lang.Integer) with arguments {w ab23?c.d,(\),1}:Unclosed group near index 3
(\) (state=42000,code=40000)
> select regexp_extract('w ab\\23?c.d','(\\\\)',1);
+------+
| _c0 |
+------+
| \ |
+------+
结果解释:
第三个SQL结果为空,直接在正则表达式中写\
表示转义空字符串,而空字符串转义后仍为空字符串,所以无结果。
第四个SQL结果报错,是因为写成\\
,第一个\将第二个\转义成反斜线字面含义,而在正则表达式中单独的一个反斜线无法直接使用,因此报错。 正则表达式中想要匹配反斜线,本身需要的是\\
(反斜线+反斜线
),第五个SQL中,第一个\
将第二个\
转义成反斜线,第三个\
将第四个\
转义成反斜线,因此代码中写的\\\\
表示的含义的反斜线+反斜线
,因此结果正则匹配到反斜线。
注意:在presto引擎中上述规则并不适用,在presto中使用自身带有\
符合的模式,代码中直接使用即可,并不需要其他额外处理。