6.6. 模式匹配

PostgreSQL 提供了三种实现模式匹配 的方法:SQL LIKE 操作符,更近一些的 SQL99 SIMILAR TO 操作符, 和POSIX-风格正则表达式. 另外还有一个模式匹配函数 SUBSTRING 可以用, 可以使用 SQL99 风格的或者 POSIX 风格的正则表达式。

技巧: 如果你的模式匹配的要求比这些还多,或者想写一些模式驱动的 替换和转换,请考虑用 Perl 或 Tcl 写一个用户定义函数.

6.6.1. LIKE

string LIKE pattern [ ESCAPE escape-character ]
string NOT LIKE pattern [ ESCAPE escape-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 LIKENOT ILIKE.所有这些操作符都是 PostgreSQL 特有的.

6.6.2. SIMILAR TOSQL99 正则表达式

string SIMILAR TO pattern [ESCAPE escape-character]
string NOT SIMILAR TO pattern [ESCAPE escape-character]

SIMILAR TO 根据自己的模式是否匹配给定字串而 返回真或者假。它和 LIKE 非常类似,只不过它 使用 SQL99 定义的正则表达式理解模式。 SQL99 的正则表达式是在 LIKE 表示法和普通的正则表达式表示法之间古怪的交叉。

类似 LIKESIMILAR TO 操作符只有在它的模式匹配整个字串的时候才能成功;这一点和普通的 正则表达式的习惯不同,在普通的正则表达式里,模式匹配字串的任意 部分。 和 LIKE 类似的地方还有,SIMILAR TO 使用 %_ 作为分别代表任意字串和任意字串字符 的通配符。(这些和 POSIX 正则表达式里的 .*. 兼容)

除了这些从 LIKE 借用的功能之外, SIMILAR TO 支持下面这些从 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

带三个参数的SUBSTRINGSUBSTRING(string FROM pattern FOR escape), 提供了一个从字串中抽取一个匹配 SQL99 正则表达式模式的子字串的函数。 和SIMILAR TO一样,声明的模式必须匹配整个数据串,否则函数失效并 返回 NULL。为了标识在成功的时候应该返回的模式部分,SQL99 要求模式 必须出现后跟双引号(")的两个逃逸字符。匹配这两个标记 之间的模式的字串将被返回。

一些例子:

SUBSTRING('foobar' FROM '%#"o_b#"%' FOR '#')   oob
SUBSTRING('foobar' FROM '#"o_b#"%' FOR '#')    NULL

6.6.3. POSIX 正则表达式

Table 6-11 列出了所有可用的 用于 POSIX 正则表达式的操作符。

Table 6-11. 正则表达式匹配操作符

操作符描述例子
~ 匹配正则表达式,大小写相关'thomas' ~ '.*thomas.*'
~* 匹配正则表达式,大小写无关'thomas' ~* '.*Thomas.*'
!~ 不匹配正则表达式,大小写相关'thomas' !~ '.*Thomas.*'
!~* 不匹配正则表达式,大小写无关'thomas' !~* '.*vadim.*'

POSIX 正则表达式提供了比 LIKESIMILAR TO 操作符 更强大的模式匹配的方法.许多 Unix 工具,比如 egrepsed,或 awk 使用一种与我们这里描述的类似的模式匹配语言.

正则表达式是一个字符序列,它是定义一个字串集合 (一个正则集合)的缩写. 如果一个字串是正则表达式描述的正则集合中的一员时, 我们就说这个字串匹配该正则表达式. 和 LIKE 一样,模式字符准确地匹配字串字符, 除非在正则表达式语言里有特殊字符 --- 不过正则表达式用的 特殊字符和 LIKE 用的不同. 和 LIKE 不一样的是,正则表达式 可以匹配字串里的任何位置,除非该正则表达式明确地挂接在字串 的开头或者结尾.

一些例子:

'abc' ~ 'abc'    true
'abc' ~ '^a'     true
'abc' ~ '(b|d)'  true
'abc' ~ '^(b|c)' false

带两个参数的SUBSTRINGSUBSTRING(string FROM pattern),提供了从字串中抽取一个匹配 POSIX 正则表达式模式的 子字串的方法。如果没有匹配它返回 NULL,否则就是文本中匹配模式的那部分。 但是如果该模式包含任何圆括弧,那么将返回匹配第一对子表达式(对应第一个左圆括弧的) 的文本。如果你想在表达式里使用圆括弧,那么你总可以在最外围放上一对儿圆括弧 来避免出现这个问题。

一些例子:

SUBSTRING('foobar' FROM 'o.b')     oob
SUBSTRING('foobar' FROM 'o(.)b')   o

正则表达式("RE"),在POSIX1003.2 中定义, 它有两种形式:现代 RE(基本上就是那些在 egrep 里的; 1003.2 称它们为"扩展"的 RE)以及过时的 RE (基本上就是那些在 ed里的; 1003.2 称它们为"基本"的 RE). PostgreSQL 实现了现代的形式.

(现代)的 RE 是一个或多个非空的 分支, 由 | 分隔.它匹配任何匹配其中一个分支的东西.

一个分支是一个或多个片段 连接而成.一个片段匹配第一个,然后后面的片段匹配第二个, 等等.

一个片段是一个原子, 后面可能跟着一个*+?,或者范围. 一个跟着 * 的原子匹配一个由零个或者更多个 匹配该原子的项组成的序列.一个跟着 + 的原子 匹配一个由一个或者更多匹配该原子的项组成的序列. 一个跟着 ? 的原子匹配一个由零个或者一个匹配该原子的项组成的序列.

范围{ 跟着一个无符号整数,可能还跟着 , 可能还跟着另外一个无符号整数,最后总是跟着一个 }. 这些整数介于 0 和 RE_DUP_MAX (255) (包含 RE_DUP_MAX) 之间,并且如果有两个整数,第一个不能大于第二个. 一个后面跟着一个包含没有逗号的整数 i 的范围的原子匹配精确的 i 个原子. 一个后面跟着一个包含一个整数 i 和一个逗号的范围的原子匹配i 个 或更多个该原子的序列. 一个后面跟着一个包含两个整数 ij的范围的原子匹配 ij (包含 j )个该原子的序列.

注意: 一个重复操作符(?*+,或者范围)后面不能跟着另外一个重复操作符. 重复操作符不能是表达式或者子表达式的开头,或者跟在 ^| 后面.

原子 可以是:一个用 () 包围的正则表达式, (对应一个该正则表达式的匹配项), 一对空括弧 () (匹配空字串), 一个方括弧表达式(见下文), .(匹配任意单个字符), ^(匹配一行开头的空字串), $(匹配一行结尾的空字串), 一个后面跟着字符 ^.[$()|*+?{\\ (匹配把这些字符当做普通字符的字符), 或者一个没有其它标注的字符(匹配该字符本身). 一个 { 后面跟着一个非数字的字符就是 一个普通字符,而不是一个范围.用\ 结束的 RE 是非法的.

请注意反斜扛(\)在字串文本里已经有特殊的 含义了,所以如果你要写一个包含一个反斜扛的模式, 你必须在查询里写两个反斜扛.

方括弧表达式是一个包围在 [] 里的字符列表.它通常匹配任意单个 列表中的字符(又见下文). 如果列表以 ^ 开头,它匹配 任意单个(又见下文)不在该列表中的字符. 如果该列表中两个字符用-隔开, 那它就是那两个字符(包括在内)之间的所有字符范围的缩写, 比如,在 ASCII[0-9] 包含任何十进制数字. 两个范围共享一个终点是非法的,比如, a-c-e.这个范围与字符集关系密切, 可移植的程序不应该依靠它们.

想在列表中包含文本 ],可以让它做 列表的首字符(可能会在一个 ^ 后面). 想在列表中包含文本 -,可以让它做 列表的首字符或者末字符,或者一个范围的第二个终点. 想在列表中把文本-当做范围的起点, 把它用 [..] 包围起来,这样它就成为一个集合元素(见下文). 除了一些这样的例外和一些用 [ 的组合(见下段)以外,所有其它特殊字符,包括 \,在方括弧表达式里都失去它们的特殊含义.

在一个方括弧表达式里,一个集合元素(一个字符,一个当做 一个字符的多字符序列,或者一个表示上面两种情况的集合序列) 包含在 [..] 里面的时候表示该集合元素的字符序列.该序列是该方括弧列表 的一个元素.因此一个包含多字符集合元素的方括弧表达式就 可以匹配多于一个字符,比如,如果集合序列包含一个 ch 集合元素, 那么 RE [[.ch.]]*c 匹配 chchcc 的头五个字符. (译注:其实把 [. 和 .] 括起来的当一个字符看就行了.)

在方括弧表达式里,在[==] 里包围的集合元素是一个等效表, 代表等于这里所有集合元素的字符序列,包括它本身. (如果没有其它等效集合元素,那么就好象封装元素是 [..].) 比如,如果 o^ 是一个等效表的成员,那么 [[=o=]][[=^=]],和 [o^] 都是同义的.一个等效表不能是一个范围的 端点.

在方括弧表达式里,在 [::] 里面封装的字符表名字代表 属于该表的所有字符的列表. 标准的字符表名字是:alnumalphablankcntrldigitgraphlowerprintpunctspaceupperxdigit. 它们代表在 ctype 里定义的字符表. 本地化设置可能会提供其他的表.字符表不能用做一个范围的端点.

在方括弧表达式里有两个特例:方括弧表达式 [[:<:]][[:>:]] 分别匹配一个单词开头和结束的空串. 单词定义为一个单词字符序列,前面和后面都没有其它单词字符. 单词字符是一个字母数字(和 ctype 里定义的一样) 或者一个下划线.这是一个扩展,兼容POSIX1003.2, 但那里面并没有说明, 而且在准备移植到其他系统里去的软件里一定要小心使用.

在一个 RE 可以匹配一个给定字串的多个子字串, RE 匹配那个在字串中最开头的那个.如果在该位置RE可以匹配多于 一个子字串,它匹配最长的.子表达式也是匹配最长的子字串, 由于有整个匹配越长越好的约束,所以靠前的子表达式的优先级比 靠后的高.请注意,高级的子表达式比低级的子表达式部件 的优先级高.

匹配长度是以字符计算的,而不是以集合元素. 空字串的长度被认为比什么都不匹配长. 比如, bb* 匹配 abbbc 的中间三个字符, (wee|week)(knights|nights) 匹配 weeknights 的所有十个字符, 而用 (.*).* 匹配 abc 的时候,圆括弧的子表达式匹配 所有三个字符,而如果用 (a*)* 匹配 bc,那么整个 RE 和圆括弧的子表达式 都匹配空字串.

如果声明了大小写无关的匹配,其效果就好象在字符集里的 大小写区别都消失了一样.如果一个字母的大写和小写都出现在方括弧表达式外边 的普通字符里,那么你可以有效的把它们转换成一个包含大小写的 方括弧表达式,比如,x 变成 [xX]. 如果它出现在方括弧表达式里面,所有它相对的大小写都被加入到 方括弧表达式,因此(比如)[x] 变成 [xX][^x] 变成[^xX]

对 RE 的长度没有特别的限制,除了内存的限制以外. 内存的使用和 RE 的尺寸近似成线性关系, 并且随着 RE 的复杂度提高而极大增加,只有范围重复是例外. 范围重复是用宏扩展实现的,如果计数非常大或者范围重复是 嵌套的,那么开销就非常大.比如,象下面这样的 RE, ((((a{1,100}){1,100}){1,100}){1,100}){1,100} 会(最终)用光几乎任何现有机器的的交换空间. [1]

Notes

[1]

不过,这些东西是 1994 年写的, 这里的数字可能会改变,但问题依旧.