4.5. 模式匹配

Postgres 提供了两种实现模式匹配 的方法:SQL LIKE 操作符和POSIX-风格正则表达式.

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

4.5.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 选择另外一个逃逸字符来避免这些.

关键字 ILIKE 可以用于替换 LIKE, 令该匹配就当前的区域设置是大小写无关的. 这个特性不是 SQL 标准,是 Postgres 扩展.

操作符 ~~ 等效于 LIKE, 而 ~~* 对应 ILIKE. 还有 !~~!~~* 操作符 分别代表 NOT LIKENOT ILIKE.所有这些都是 Postgres 特有的.

4.5.2. POSIX 正则表达式

Table 4-8. 正则表达式匹配操作符

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

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

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

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

(现代)的 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(3) 里定义的字符表. 本地化设置可能会提供其他的表.字符表不能用做一个范围的端点.

在方括弧表达式里有两个特例:方括弧表达式 [[:<:]][[:>:]] 分别匹配一个单词开头和结束的空串. 单词定义为一个单词字符序列,前面和后面都没有其它单词字符. 单词字符是一个字母数字(和 ctype(3) 里定义的一样) 或者一个下划线.这是一个扩展,兼容 POSIX 1003.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 年写的, 这里的数字可能会改变,但问题依旧.