6.6. 模式匹配 #
PostgreSQL 提供了三种独立的模式匹配方法:传统的 SQL
LIKE 操作符、较新的
SQL99
SIMILAR TO 操作符,以及
POSIX 风格的正则表达式。此外还有一个模式匹配函数
SUBSTRING 可用,它可以使用
SQL99 风格或 POSIX 风格的正则表达式。
提示
如果你的模式匹配的要求超出了这些,请考虑用 Perl 或 Tcl 写一个用户定义的函数。
6.6.1. LIKE #
stringLIKEpattern[ESCAPEescape-character]stringNOT LIKEpattern[ESCAPEescape-character]
每个 pattern(模式)都定义了一个字符串集合。如果 string(字符串)包含在
pattern 所表示的字符串集合中,LIKE 表达式就返回真。(与预期的一样,如果 LIKE 返回真,NOT LIKE
表达式就返回假,反之亦然。等价的表达式是
NOT (。)
string LIKE
pattern)
如果 pattern 不包含百分号或下划线,那么该模式只表示字符串本身;此时 LIKE
的行为与等于操作符相同。pattern 中的下划线(_)代表(匹配)任意单个字符;百分号(%)匹配任意零个或多个字符的字符串。
一些示例:
'abc' LIKE 'abc' true 'abc' LIKE 'a%' true 'abc' LIKE '_b_' true 'abc' LIKE 'c' false
LIKE 模式匹配总是覆盖整个字符串。要匹配字符串中任意位置的模式,模式必须以百分号开头并以百分号结尾。
要匹配字面量下划线或百分号而不是把它们当作通配符,pattern中相应的字符前面必须带有转义字符。默认的转义字符是反斜线,但也可以使用ESCAPE子句选择其他转义字符。要匹配转义字符本身,请写两个转义字符。
注意,反斜线在字符串常量中本来就有特殊含义,因此要写一个包含反斜线的模式常量,必须在查询中写两个反斜线。这样,要写一个实际匹配字面量反斜线的模式,就意味着在查询中写四个反斜线。可以通过ESCAPE选择不同的转义字符来避免这一点;这样反斜线对LIKE就不再是特殊字符。(但反斜线对字符串常量解析器仍然是特殊字符,因此你仍需要写两个。)
也可以通过写 ESCAPE '' 来选择不使用转义字符。这会禁用转义机制,从而无法关闭模式中下划线和百分号的特殊含义。
可以使用关键字 ILIKE 代替
LIKE,按照当前的区域设置使匹配不区分大小写。这不是 SQL 标准的内容,而是
PostgreSQL 的扩展。
操作符~~等效于
LIKE,而~~*对应
ILIKE。还有 !~~ 和 !~~* 操作符分别代表
NOT LIKE 和 NOT
ILIKE。所有这些操作符都是 PostgreSQL 特有的。
6.6.2. SIMILAR TO 和 SQL99
正则表达式 #
stringSIMILAR TOpattern[ESCAPEescape-character]stringNOT SIMILAR TOpattern[ESCAPEescape-character]
SIMILAR TO 操作符根据其模式是否匹配给定字符串返回真或假。它与 LIKE 非常相似,区别在于它使用 SQL99 对正则表达式的定义来解释模式。SQL99 的正则表达式是
LIKE 记法和常用正则表达式记法的一种奇特交叉。
与LIKE类似,SIMILAR TO
操作符只有在其模式匹配整个字符串时才算成功;这一点不同于普通正则表达式,后者可以匹配字符串的任意部分。与
LIKE 相同,SIMILAR TO 使用
% 和 _ 作为通配符,分别表示任意字符串和任意单个字符(分别类似于 POSIX 正则表达式中的 .* 和
.)。
除了这些从LIKE借用的功能之外,SIMILAR TO 支持下面这些从 POSIX 正则表达式借用的模式匹配元字符:
|表示选择(两个候选之一)。*表示重复前面的项零次或更多次。+表示重复前面的项一次或更多次。可以使用圆括号
()把多个项组合成一个逻辑项。一个方括号表达式
[...]指定一个字符类,就像 POSIX 正则表达式一样。
注意,有界重复(? 和 {...})没有提供,尽管它们存在于 POSIX 中。另外,点号(.)不是元字符。
与LIKE一样,反斜杠会禁用这些元字符的特殊含义;也可以用 ESCAPE 指定其他转义字符。
下面是一些示例:
'abc' SIMILAR TO 'abc' true 'abc' SIMILAR TO 'a' false 'abc' SIMILAR TO '%(b|d)%' true 'abc' SIMILAR TO '(b|c)%' false
带三个参数的 SUBSTRING 函数
SUBSTRING(
提取与 SQL99 正则表达式模式匹配的子串。与 string FROM
pattern FOR
escape)SIMILAR TO 一样,指定的模式必须匹配整个数据字符串,否则函数失败并返回空值。要指明模式中成功时应当返回的部分,SQL99 规定模式必须包含两处后面跟着双引号(")的转义字符。返回的文本是与这两个标记之间的模式部分匹配的文本。
下面是一些示例:
SUBSTRING('foobar' FROM '%#"o_b#"%' FOR '#') oob
SUBSTRING('foobar' FROM '#"o_b#"%' FOR '#') NULL
6.6.3. POSIX正则表达式 #
表 6.11列出了所有可用于 POSIX 正则表达式模式匹配的操作符。
表 6.11. 正则表达式匹配操作符
| 操作符 | 描述 | 示例 |
|---|---|---|
~ | 匹配正则表达式,区分大小写 | 'thomas' ~ '.*thomas.*' |
~* | 匹配正则表达式,不区分大小写 | 'thomas' ~* '.*Thomas.*' |
!~ | 不匹配正则表达式,区分大小写 | 'thomas' !~ '.*Thomas.*' |
!~* | 不匹配正则表达式,不区分大小写 | 'thomas' !~* '.*vadim.*' |
POSIX正则表达式提供了比LIKE和SIMILAR TO操作符更强大的模式匹配方式。许多 Unix 工具,例如egrep、sed或awk,都使用与这里描述的模式匹配语言相似的语言。
正则表达式是一个字符序列,它是一个字符串集合(正则集)的缩写定义。如果一个字符串是正则表达式所描述的正则集中的成员,就说该字符串匹配这个正则表达式。与 LIKE 一样,模式字符与字符串字符精确匹配,除非它们是正则表达式语言中的特殊字符——
但正则表达式使用的特殊字符与 LIKE 不同。与 LIKE 模式不同的是,除非正则表达式被显式地锚定到字符串的开头或结尾,否则正则表达式可以匹配字符串中任意位置的内容。
下面是一些示例:
'abc' ~ 'abc' true 'abc' ~ '^a' true 'abc' ~ '(b|d)' true 'abc' ~ '^(b|c)' false
带两个参数的 SUBSTRING 函数
SUBSTRING(
提取与 POSIX 正则表达式模式匹配的子串。如果没有匹配则返回空值,否则返回与模式匹配的文本部分。但是,如果模式包含任何圆括号,则返回与第一个带圆括号的子表达式(左圆括号最先出现的那个)匹配的文本部分。如果想在表达式中使用圆括号而又不触发这一例外,总可以把整个表达式用圆括号括起来。
string FROM
pattern)
下面是一些示例:
SUBSTRING('foobar' FROM 'o.b') oob
SUBSTRING('foobar' FROM 'o(.)b') o
正则表达式(RE)按
POSIX
1003.2 的定义有两种形式:现代 RE(大致相当于
egrep 的那些;1003.2 称之为“扩展” RE)和过时的 RE(大致相当于
ed 的那些;1003.2 的“基本” RE)。PostgreSQL 实现的是现代形式。
一个(现代)RE 是一个或多个非空分支,由
| 分隔。它能匹配其中任何一个分支所能匹配的内容。
一个分支是一个或多个片段的串接。它匹配第一个片段的一个匹配后跟第二个片段的一个匹配,依此类推。
一个片段是一个原子,后面可能跟着一个
*、+、? 或界限。原子后跟 * 匹配该原子的 0 个或多个匹配组成的序列。原子后跟 +
匹配该原子的 1 个或多个匹配组成的序列。原子后跟 ? 匹配该原子的 0 个或 1 个匹配组成的序列。
一个界限是 { 后跟一个无符号十进制整数,后面可能跟
,,再后面可能跟另一个无符号十进制整数,最后总是跟 }。这些整数必须在 0 到 RE_DUP_MAX(255)之间(含),如果有两个整数,第一个不能超过第二个。原子后跟含一个整数 i 而没有逗号的界限匹配恰好 i 个该原子的匹配组成的序列。原子后跟含一个整数 i 和一个逗号的界限匹配 i 个或更多个该原子的匹配组成的序列。原子后跟含两个整数 i 和 j 的界限匹配 i 到 j(含)个该原子的匹配组成的序列。
注意
重复操作符(?、*、+ 或界限)不能跟在另一个重复操作符后面。重复操作符不能作为表达式或子表达式的开头,也不能跟在
^ 或 | 后面。
一个原子是一个由
() 括起来的正则表达式(匹配该正则表达式的一个匹配)、一个空的 ()(匹配空串)、一个方括号表达式(见下文)、.(匹配任意单个字符)、^(匹配输入字符串开头的空串)、$(匹配输入字符串结尾的空串)、一个 \ 后跟
^.[$()|*+?{\ 中的一个字符(匹配作为普通字符的该字符)、一个 \
后跟其他任何字符(匹配作为普通字符的该字符,如同 \
不存在一样),或者一个没有其他含义的单个字符(匹配该字符)。一个 { 后跟非数字字符时是普通字符,不是界限的开始。以
\ 结束一个 RE 是非法的。
注意,反斜线(\)在字符串常量中本来就有特殊含义,因此要写一个包含反斜线的模式常量,必须在查询中写两个反斜线。
方括号表达式是一个由
[] 括起来的字符列表。它通常匹配列表中的任意单个字符(但见下文)。如果列表以
^ 开头,则匹配任意一个(但见下文)不在列表其余部分中的单个字符。如果列表中的两个字符由
- 分隔,这是按整理序列位于这两个字符之间(含)的完整字符范围的简写形式,例如在 ASCII 中
[0-9] 匹配任意十进制数字。两个范围共享一个端点是非法的,例如
a-c-e。范围非常依赖于整理序列,因此可移植的程序应当避免依赖它们。
要在列表中包含字面字符 ],可以让它作为第一个字符(跟在可能的 ^ 之后)。要包含字面字符 -,可以让它作为第一个或最后一个字符,或者一个范围的第二个端点。要把字面
- 用作范围的第一个端点,可以把它放在 [. 和 .] 中使它成为一个排序元素(见下文)。除了这些和某些使用 [ 的组合(见下一段)之外,所有其他特殊字符,包括
\,在方括号表达式内都失去它们的特殊含义。
在一个方括号表达式里,一个排序元素(一个字符、一个被当做一个单一字符排序的多字符序列或者一个表示上面两种情况的排序序列名称)包含在[.和.]里面的时候表示该排序元素的字符序列。该序列被当做该方括号列表的一个单一元素。这允许一个包含多字符排序元素的方括号表达式去匹配多于一个字符,例如,如果排序序列包含一个ch排序元素,那么 RE [[.ch.]]*c匹配chchcc的头五个字符。
在方括号表达式里,包围在[=和=]里的排序元素是一个等价类,代表等效于那一个的所有排序元素的字符序列,包括它本身(如果没有其它等效排序元素,那么就好像封装定界符是[.和 .])。例如,如果o和^是一个等价类的成员,那么[[=o=]]、[[=^=]]和[o^]都是同义的。一个等价类不能是一个范围的端点。
在方括号表达式中,用 [: 和 :] 括起的字符类名表示属于该类的所有字符的列表。标准字符类名包括:alnum、alpha、blank、cntrl、digit、graph、lower、print、punct、space、upper、xdigit。它们表示 ctype 中定义的字符类。区域设置可以提供其他字符类。字符类不能用作范围的端点。
方括号表达式有两个特例:方括号表达式 [[:<:]] 和
[[:>:]] 分别匹配一个单词开头和结尾的空串。单词定义为前后都没有单词字符的单词字符序列。单词字符是 alnum 字符(由
ctype
定义)或下划线。这是一个与
POSIX 1003.2 兼容但未由其规定的扩展,在打算移植到其他系统的软件中应谨慎使用。
在一个 RE 可以匹配给定字符串的多个子串的情况下,RE 匹配在字符串中最先开始的那个。如果 RE 可以匹配从该点开始的多个子串,它匹配最长的那个。在整个匹配尽可能长的约束之下,子表达式也匹配最长的可能子串,在 RE 中靠前的子表达式比靠后的优先。注意,高层子表达式因而比其低层的组成子表达式优先。
匹配长度是以字符衡量的,而不是以排序元素。空串被认为比完全不匹配长。例如,bb* 匹配
abbbc 的中间三个字符,(wee|week)(knights|nights) 匹配
weeknights 的全部十个字符,当
(.*).* 与
abc 匹配时,带圆括号的子表达式匹配全部三个字符,而当 (a*)* 与
bc 匹配时,整个 RE 和带圆括号的子表达式都匹配空串。
如果指定不区分大小写的匹配,其效果非常类似于字母表中的所有大小写差别都消失了。当存在多种大小写形式的字母作为普通字符出现在方括号表达式之外时,它实际上被转换为包含两种大小写形式的方括号表达式,例如 x 变成 [xX]。当它出现在方括号表达式内部时,它的所有大小写对应字符都被加入该方括号表达式,因此(例如)[x] 变成 [xX],[^x] 变成 [^xX]。
RE 的长度没有特定的限制,除了内存有限之外。内存使用量与 RE
大小大致成线性关系,并且很大程度上对 RE 的复杂度不敏感,有界重复除外。有界重复通过宏展开实现,如果计数很大或有界重复嵌套,它在时间和空间上的代价都很高。像
((((a{1,100}){1,100}){1,100}){1,100}){1,100}
这样的 RE 会(最终)耗尽几乎任何现有机器的交换空间。
[3]