rexp_like-似乎无法理解其工作原理

时间:2019-05-15 14:14:33

标签: sql regex oracle12c

我得到了一个带有“令牌”列的表,该表应该仅包含字母,到目前为止,它已经通过简单的regexp_like约束来强制执行,但是最近我注意到了一个特殊的问题: 不允许使用字母“ a”(小写A)。 据我所知,所有其他标准ASCII字母都可以工作。

数据库为:

Oracle Database 12c企业版12.1.0.2.0版-64位
 生产PL / SQL版本12.1.0.2.0-生产

SELECT CHR(num), num
FROM (
  SELECT LEVEL AS num FROM dual CONNECT BY LEVEL <= 200
) WHERE REGEXP_LIKE(CHR(num), '^[A-Z]+$');

使用匹配模式'c'或'i',仅使用[a-z]或其他变体即可得出正确的结果,但是仅简单的[A-Z]省略了'a'并仅省略了该字母。

编辑: 是的,使用[a-zA-Z]可以覆盖所有字符,与使用REGEXP_LIKE(CHR(num), '^[A-Z]+$', 'i')一样('i'是不区分大小写的match_pattern),但问题是为什么这里只省略了小写字母'a'。 我们从这些标记生成一些其他字符串和常量,并且在我们的数据库中都具有类似的约束,所以我不仅要解决此问题,还希望了解导致问题的原因。

2 个答案:

答案 0 :(得分:1)

可以重现以下内容

ALTER SESSION SET NLS_SORT = FRENCH;

SELECT CHR(num), num FROM (
  SELECT LEVEL AS num FROM dual CONNECT BY LEVEL <= 200
) WHERE REGEXP_LIKE(CHR(num), '^[a-z]+$')
order by CHR(num);

比较
ALTER SESSION SET NLS_SORT = BINARY;

SELECT CHR(num), num FROM (
  SELECT LEVEL AS num FROM dual CONNECT BY LEVEL <= 200
) WHERE REGEXP_LIKE(CHR(num), '^[a-z]+$')
order by CHR(num);

似乎regexp字符集位于NLS_SORT参数之后

答案 1 :(得分:0)

使用[A-Z]应该只包括大写字符,而排除所有小写字符。至少,这就是应该做的事情,并且要做的是-用英语进行二进制排序:

alter session set nls_language = 'ENGLISH';

select * from nls_session_parameters where parameter = 'NLS_SORT';

PARAMETER                      VALUE                                   
------------------------------ ----------------------------------------
NLS_SORT                       BINARY                                  

SELECT CHR(num), num
FROM (
  SELECT LEVEL AS num FROM dual CONNECT BY LEVEL <= 200
) WHERE REGEXP_LIKE(CHR(num), '^[A-Z]+$');

C        NUM
- ----------
A         65
...
Z         90

26 rows selected. 

德语(或其他任何修改默认排序顺序的语言)都按照我的描述进行操作:

alter session set nls_language = 'GERMAN';

select * from nls_session_parameters where parameter = 'NLS_SORT';

PARAMETER                      VALUE                                   
------------------------------ ----------------------------------------
NLS_SORT                       GERMAN                                  

SELECT CHR(num), num
FROM (
  SELECT LEVEL AS num FROM dual CONNECT BY LEVEL <= 200
) WHERE REGEXP_LIKE(CHR(num), '^[A-Z]+$');

C        NUM
- ----------
A         65
...
X         88
Y         89
Z         90
b         98
c         99
d        100
...
z        122

切换到字符类可以使其行为更加一致:

alter session set nls_language = 'GERMAN';

SELECT CHR(num), num
FROM (
  SELECT LEVEL AS num FROM dual CONNECT BY LEVEL <= 200
) WHERE REGEXP_LIKE(CHR(num), '^[[:upper:]]+$');

C        NUM
- ----------
A         65
...
Z         90

26 rows selected. 

如果要包含所有大写和小写字符,则可以使用[:alpha:]代替[:upper:]。请注意,这两个都将包含变音符号(loweralpha将包含“ß”);可能不理想:

SELECT character
FROM (
  SELECT CHR(LEVEL + 64) AS character
  FROM dual CONNECT BY LEVEL <= 58
  UNION ALL SELECT column_value
  FROM TABLE(sys.odcivarchar2list('ß', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'))
)
WHERE REGEXP_LIKE(character, '^[[:upper:]]+$')
ORDER BY character;

CHARACTER
---------
A
Ä
B
...

这是in the documentation的说明。

  

传统的正则表达式引擎旨在仅处理英文文本。但是,正则表达式实现可以包含多种语言,这些语言的特征与西欧文本有很大不同。 Oracle数据库中正则表达式的实现基于Unicode正则表达式准则。 REGEXP SQL函数可与支持数据库字符集和国家字符集的所有字符集一起使用。此外,Oracle数据库增强了POSIX正则表达式结构的匹配功能,可满足匹配多语言数据的独特语言要求。

  

根据POSIX标准,正则表达式中的范围包括当前语言环境的语言定义中范围的起点和终点之间的所有排序规则元素。因此,正则表达式中的范围是语言范围,而不是字节值范围...

     

... Oracle数据库按照NLS_SORT参数的指定来解释范围表达式,以确定给定范围所覆盖的排序规则元素

Linguistic collation本质上是基于语言的行为方式或期望用户的行为方式。

如果您使用二进制排序,则仅根据每个字符的代码点(ASCII / Unicode值)对字符进行排序; “ a”为97,明显比“ A”为65(数字高)。(如果选择所有字符并使用二进制排序,您会看到:

alter session set nls_language = 'GERMAN';
alter session set nls_sort = 'BINARY';

SELECT CHR(LEVEL + 64) AS character, NLSSORT(CHR(LEVEL + 64)) AS sort
FROM dual CONNECT BY LEVEL <= 58
UNION ALL SELECT column_value, NLSSORT(column_value)
FROM TABLE(sys.odcivarchar2list('ß', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'))
ORDER BY character;

CHARACTER SORT     
--------- ---------
A         4100     
B         4200     
C         4300     
...
X         5800     
Y         5900     
Z         5A00     
[         5B00     
\         5C00     
]         5D00     
^         5E00     
_         5F00     
`         6000     
a         6100     
b         6200     
c         6300    
... 
x         7800     
y         7900     
z         7A00     
Ä         C38400   
Ö         C39600   
Ü         C39C00   
ß         C39F00   
ä         C3A400   
ö         C3B600   
ü         C3BC00   

65 rows selected. 

The nlssort() function让您看到Oracle用于排序的实际值-这是字符的二进制值,因此'A'仍然是65(很好,十六进制为41)加上一个空字节。当您按那些“ a”排序时,仍然在“ A”之后。

使用语言排序规则时,这些排序值完全不同:

alter session set nls_sort = 'GERMAN';

SELECT CHR(LEVEL + 64) AS character, NLSSORT(CHR(LEVEL + 64)) AS sort
FROM dual CONNECT BY LEVEL <= 58
UNION ALL SELECT column_value, NLSSORT(column_value)
FROM TABLE(sys.odcivarchar2list('ß', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'))
ORDER BY character;

CHARACTER SORT     
--------- ---------
[         00005B00 
\         00005C00 
]         00005D00 
^         00005E00 
_         00005F00 
`         00006000 
a         14000100 
A         14000200 
ä         14000900 
Ä         14000A00 
b         19000100 
B         19000200 
c         1E000100 
C         1E000200 
...
r         64000100 
R         64000200 
s         69000100 
S         69000300 
ß         69004400 
t         6E000100 
T         6E000200 
u         73000100 
U         73000200 
ü         73000900 
Ü         73000A00 
v         78000100 
V         78000200 
w         7A000100 
W         7A000200 
x         7D000100 
X         7D000200 
y         82000100 
Y         82000200 
z         87000100 
Z         87000200 

65 rows selected. 

“语言”部分表示“ a”的所有变体在一起-依次是“ a”,“ A”,“ä”和“Ä”。然后,所有“ b”变体等都带有特定于语言的字符及其通用关系。 including examples文档中有更多有关此的内容。

该顺序说明了您所看到的行为。再次查看该排序列表的开头:

CHARACTER SORT     
--------- ---------
...
a         14000100 
A         14000200 
ä         14000900 
Ä         14000A00 
b         19000100 
B         19000200 
c         1E000100 
C         1E000200 

如果您尝试使用范围为[A-Z]的正则表达式,则它以'A'开头,但顺序为 'a'之后-以便降低-case'a'被排除。同样,如果您使用[B-Z],则从'B'开始,这是所有'a'变体和小写字母'b'的缩写,因此现在将其排除在外。