PostgreSQL 模式匹配

2021-08-26 18:09 更新
9.7.1. LIKE
9.7.2. SIMILAR TO正則表達式
9.7.3. POSIX正則表達式

PostgreSQL提供了三種獨立的實現(xiàn)模式匹配的方法:SQL LIKE操作符、更近一些的SIMILAR TO操作符(SQL:1999 里添加進來的)和POSIX-風格的正則表達式。除了這些基本的 這個串匹配這個模式嗎?操作符外,還有一些函數(shù)可用于提取或替換匹配子串并在匹配位置分離一個串。

提示

如果你的模式匹配的要求超出了這些,請考慮用 Perl 或 Tcl 寫一個用戶定義的函數(shù)。

小心

雖然大部分的正則表達式搜索都能被很快地執(zhí)行,但是正則表達式仍可能被 人為地弄成需要任意長的時間和任意量的內(nèi)存進行處理。要當心從不懷好意 的來源接受正則表達式搜索模式。如果必須這樣做,建議加上語句超時限制。

使用SIMILAR TO模式的搜索具有同樣的安全性危險, 因為SIMILAR TO提供了很多和 POSIX-風格正則表達式相同的能力。

LIKE搜索比其他兩種選項簡單得多,因此在使用 不懷好意的模式來源時要更安全些。

這三種類型的模式匹配算子都不支持非確定性拼貼。 如果需要的話,可以在表達式中應用不同的拼貼來繞過這個限制。

9.7.1. LIKE

string LIKE pattern [ESCAPE escape-character]
string NOT LIKE pattern [ESCAPE escape-character]

如果該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模式匹配總是覆蓋整個串。因此,要匹配在串內(nèi)任何位置的序列,該模式必須以百分號開頭和結尾。

要匹配文本的下劃線或者百分號,而不是匹配其它字符, 在pattern里相應的字符必須 前導逃逸字符。缺省的逃逸字符是反斜線,但是你可以用ESCAPE子句指定一個不同的逃逸字符。 要匹配逃逸字符本身,寫兩個逃逸字符。

注意

如果你關掉了standard_conforming_strings,你在文串常量中寫的任何反斜線都需要被雙寫。詳見第 4.1.2.1 節(jié)。

請注意反斜線在串文本里已經(jīng)有特殊含義了,所以如果你寫一個 包含反斜線的模式常量,那你就要在 SQL 語句里寫兩個反斜線。 因此,寫一個匹配單個反斜線的模式實際上要在語句里寫四個反斜線。 你可以通過用 ESCAPE 選擇一個不同的逃逸字符 來避免這樣;這樣反斜線就不再是 LIKE 的特殊字符了。 但仍然是字符文本分析器的特殊字符,所以你還是需要兩個反斜線。) 我們也可以通過寫ESCAPE ''的方式不選擇逃逸字符,這樣可以有效地禁用逃逸機制,但是沒有辦法關閉下劃線和百分號在模式中的特殊含義。

根據(jù)SQL標準,省略ESCAPE意味著沒有轉義字符(而不是默認為反斜杠),并且不允許使用零長度的ESCAPE值。 因此,PostgreSQL在這方面的行為有點不標準。

關鍵字ILIKE可以用于替換LIKE, 它令該匹配根據(jù)活動區(qū)域成為大小寫無關。這個不屬于SQL標準而是一個PostgreSQL擴展。

操作符~~等效于LIKE, 而~~*對應ILIKE。 還有 !~~!~~*操作符分別代表NOT LIKENOT ILIKE。 所有這些操作符都是PostgreSQL特有的。 你可能會在EXPLAIN輸出和類似的地方看到這些操作符名稱,因為解析器實際上將LIKE等翻譯成這些運算符。

短語LIKE,ILIKENOT LIKE,和 NOT ILIKEPostgreSQL語法中通常被視為操作符; 例如,它們可以用于expression operator 的任何(subquery)構造,盡管這里不能包含ESCAPE子句。 在某些晦澀難懂的情況下,可能需要用底層操作符名稱替代。

還可參見前綴操作符^@和相應的starts_with函數(shù),在需要簡單匹配字符串開頭的情況下比較有用。

9.7.2. SIMILAR TO正則表達式

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

SIMILAR TO操作符根據(jù)自己的模式是否匹配給定串而返回真或者假。 它和LIKE非常類似,只不過它使用 SQL 標準定義的正則表達式理解模式。 SQL 正則表達式是在LIKE標記和普通的(POSIX)正則表達式標記的奇怪的雜交。

類似LIKESIMILAR TO操作符只有在它的模式匹配整個串的時候才能成功;這一點和普通的 正則表達式的行為不同,在普通的正則表達式里,模式匹配串的任意部分。 和LIKE類似的地方還有,SIMILAR TO使用_%作為分別代表任意單個字符和任意串的通配符(這些可以比得上 POSIX 正則表達式里的..*)。

除了這些從LIKE借用的功能之外,SIMILAR TO支持下面這些從 POSIX 正則表達式借用的 模式匹配元字符:

  • |表示選擇(兩個候選之一)。

  • *表示重復前面的項零次或更多次。

  • +表示重復前面的項一次或更多次。

  • ?表示重復前面的項零次或一次。

  • {m}表示重復前面的項剛好m次。

  • {m,}表示重復前面的項m次或更多次。

  • {m,n}表示重復前面的項至少m次并且不超過n次。

  • 可以使用圓括號()把多個項組合成一個邏輯項。

  • 一個方括號表達式[...]聲明一個字符類,就像 POSIX 正則表達式一樣。

注意點號(.)不是SIMILAR TO的一個元字符。

LIKE一樣,反斜杠將禁用這些元字符的特殊含義。 可以用 ESCAPE 來指定不同的轉義字符,或者可以通過寫 ESCAPE '' 來禁用轉義功能。

根據(jù)SQL標準,省略ESCAPE意味著沒有轉義字符(而不是默認為反斜杠),并且不允許使用零長度的ESCAPE值。 PostgreSQL在這方面的行為有點不標準。

另一個非標準擴展是,在轉義字符后面跟著一個字母或數(shù)字提供了對為POSIX正則表達式定義的轉義序列的訪問;參見下面的 表 9.20,表 9.21,和 表 9.22 。

一些例子:

'abc' SIMILAR TO 'abc'          true
'abc' SIMILAR TO 'a'            false
'abc' SIMILAR TO '%(b|d)%'      true
'abc' SIMILAR TO '(b|c)%'       false
'-abc-' SIMILAR TO '%\mabc\M%'  true
'xabcy' SIMILAR TO '%\mabc\M%'  false

帶有三個參數(shù)的substring函數(shù)可以提取匹配SQL正則表達式模式的子字符串。 該函數(shù)可以按照SQL99語法編寫。

substring(string from pattern for escape-character)

或作為一個普通的三參數(shù)函數(shù):

substring(string, pattern, escape-character)

SIMILAR TO一樣,指定的模式必須與整個數(shù)據(jù)字符串匹配,否則函數(shù)失敗并返回空值。 為了表示匹配的數(shù)據(jù)子字符串的模式中,模式中應該包含兩個轉義字符的出現(xiàn),并在后面加上一個雙引號(")。 匹配成功后,將返回與這些分隔符之間的模式部分匹配的文本。

轉義-雙引號分隔符實際上是 將子字符串的模式分成三個獨立的 正則表達式;例如,豎條(|) 三節(jié)中的任何一節(jié)只影響到該節(jié)。 此外,第一節(jié)和第三種正則表達式的定義是為了匹配最小的 盡可能多的文字,而不是最大的文字,當有歧義的時候,就不應該是最大的文字。關于有多少數(shù)據(jù)字符串符合哪種模式。 (在POSIX術語中,第一和第三種正則表達式被強行規(guī)定為非貪婪)。

作為對SQL標準的擴展,PostgreSQL只允許有一個轉義雙引號分隔符,在這種情況下,第三個正則表達式被視為空;或者沒有分隔符,在這種情況下,第一個和第三個正則表達式被視為空。

一些例子,使用#"定界返回串:

substring('foobar' from '%#"o_b#"%' for '#')   oob
substring('foobar' from '#"o_b#"%' for '#')    NULL

9.7.3. POSIX正則表達式

表 9.16列出了所有可用于 POSIX 正則表達式模式匹配的操作符。

表 9.16. 正則表達式匹配操作符

操作符

描述

例子

text ~ textboolean

字符串匹配正則表達式,大小寫敏感

'thomas' ~ '.*thom.*'t

text ~* textboolean

字符串匹配正則表達式,大小寫不敏感

'thomas' ~* '.*Thom.*'t

text !~ textboolean

字符串不匹配正則表達式,大小寫敏感

'thomas' !~ '.*thomas.*'f

text !~* textboolean

字符串不匹配正則表達式,大小寫不敏感

'thomas' !~* '.*vadim.*'t


POSIX正則表達式提供了比LIKESIMILAR TO操作符更強大的含義。許多 Unix 工具,例如egrep、sedawk使用一種與我們這里描述的類似的模式匹配語言。

正則表達式是一個字符序列,它是定義一個串集合 (一個正則集)的縮寫。 如果一個串是正則表達式描述的正則集中的一員時, 我們就說這個串匹配該正則表達式。 和LIKE一樣,模式字符準確地匹配串字符, 除非在正則表達式語言里有特殊字符 — 不過正則表達式用的 特殊字符和LIKE用的不同。 和LIKE模式不一樣的是,正則表達式允許匹配串里的任何位置,除非該正則表達式顯式地掛接在串的開頭或者結尾。

一些例子:

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

POSIX模式語言的詳細描述見下文。

帶兩個參數(shù)的substring函數(shù),即substring(string from pattern),提供了抽取一個匹配 POSIX 正則表達式模式的子串的方法。如果沒有匹配它返回空值,否則就是文本中匹配模式的那部分。 但是如果該模式包含任何圓括號,那么將返回匹配第一對子表達式(對應第一個左圓括號的) 的文本。如果你想在表達式里使用圓括號而又不想導致這個例外,那么你可以在整個表達式外邊放上一對圓括號。 如果你需要在想抽取的子表達式前有圓括號,參閱后文描述的非捕獲性圓括號。

一些例子:

substring('foobar' from 'o.b')     oob
substring('foobar' from 'o(.)b')   o

regexp_replace函數(shù)提供了將匹配 POSIX 正則表達式模式的子串替換為新文本的功能。 它的語法是 regexp_replace(source, pattern, replacement [ , flags ])。 如果沒有匹配pattern,那么返回不加修改的source串。 如果有匹配,則返回的source串里面的匹配子串將被replacement串替換掉。 replacement 串可以包含\n, 其中\n是 1 到 9, 表明源串里匹配模式里第n個圓括號子表達式的子串應該被插入, 并且它可以包含\&表示應該插入匹配整個模式的子串。如果你需要放一個文字形式的反斜線在替換文本里,那么寫 \\。flags參數(shù)是一個可選的文本串,它包含另個或更多單字母標志,這些標志可以改變函數(shù)的行為。標志i指定大小寫無關的匹配,而標志g指定替換每一個匹配的子串而不僅僅是第一個。支持的標志(但不是g)在表 9.24中描述。

一些例子:

regexp_replace('foobarbaz', 'b..', 'X')
                                   fooXbaz
regexp_replace('foobarbaz', 'b..', 'X', 'g')
                                   fooXX
regexp_replace('foobarbaz', 'b(..)', 'X\1Y', 'g')
                                   fooXarYXazY

regexp_match返回一個文本數(shù)組,它包含一個POSIX正則表達式模式與一個字符串第一個匹配所得到的子串。其語法是regexp_match(string, pattern [, flags ])。如果沒有匹配,則結果為 NULL。如果找到一個匹配并且pattern不包含帶括號的子表達式,那么結果是一個單一元素的文本數(shù)組,其中包含匹配整個模式的子串。如果找到一個匹配并且pattern含有帶括號的子表達式,那么結果是一個文本數(shù)組,其中第n個元素是與 pattern 的第n個圓括號子表達式匹配的子串(非捕獲圓括號不計入在內(nèi),詳見下文)。flags參數(shù)是一個可選的文本字符串,它包含零個或者更多個可以改變該函數(shù)行為的單字母標志。所支持的標志在表 9.24中介紹。

一些例子:

SELECT regexp_match('foobarbequebaz', 'bar.*que');
 regexp_match
--------------
 {barbeque}
(1 row)

SELECT regexp_match('foobarbequebaz', '(bar)(beque)');
 regexp_match
--------------
 {bar,beque}
(1 row)

在通常情況下,人們只是想要的大整個匹配的子串或者NULL(沒有匹配),可以寫成這樣

SELECT (regexp_match('foobarbequebaz', 'bar.*que'))[1];
 regexp_match
--------------
 barbeque
(1 row)

regexp_matches函數(shù)返回一個文本數(shù)組的集合,其中包含著一個POSIX正則表達式模式與一個字符串匹配得到的子串。它和regexp_match具有相同的語法。如果沒有匹配,這個函數(shù)不會返回行。如果有一個匹配并且給定了g標志,則返回一行。如果有N個匹配并且給定了 g標志,則返回N行。每一個返回的行都是一個文本數(shù)組,其中含有整個匹配的子串或者匹配pattern的圓括號子表達式的子串,這和上面對regexp_match的介紹一樣。regexp_matches接受 表 9.24中展示的所有標志,外加令它返回所有匹配而不僅僅是第一個匹配的g標志。

一些例子:

SELECT regexp_matches('foo', 'not there');
 regexp_matches
----------------
(0 rows)

SELECT regexp_matches('foobarbequebazilbarfbonk', '(b[^b]+)(b[^b]+)', 'g');
 regexp_matches
----------------
 {bar,beque}
 {bazil,barf}
(2 rows)

提示

在大部分情況下,regexp_matches()應該與g標志一起使用,因為如果只是想要第一個匹配,使用regexp_match()會更加簡單高效。不過,regexp_match()僅存在于PostgreSQL版本10以及更高的版本中。當在較老的版本中使用時,一種常用的技巧是把 regexp_matches()調(diào)用放在子選擇中,例如:

SELECT col1, (SELECT regexp_matches(col2, '(bar)(beque)')) FROM tab;

如果有一個匹配,則這個語句會產(chǎn)生一個文本數(shù)組,否則返回NULL,這和regexp_match()的做法一樣。如果沒有子選擇,這個查詢對于沒有匹配的表行根本不會產(chǎn)生輸出,顯然那不是想要的行為。

regexp_split_to_table把一個 POSIX 正則表達式模式當作一個定界符來分離一個串。它的語法形式是regexp_split_to_table(string, pattern [, flags ])。如果沒有與 pattern 的匹配,該函數(shù)返回string。如果有至少有一個匹配,對每一個匹配它都返回從上一個匹配的末尾(或者串的開頭)到這次匹配開頭之間的文本。當沒有更多匹配時,它返回從上一次匹配的末尾到串末尾之間的文本。flags參數(shù)是一個可選的文本串,它包含零個或更多單字母標志,這些標識可以改變該函數(shù)的行為。regexp_split_to_table能支持的標志在 表 9.24中描述。

regexp_split_to_array函數(shù)的行為和regexp_split_to_table相同,不過regexp_split_to_array會把它的結果以一個text數(shù)組的形式返回。它的語法是regexp_split_to_array( string , pattern [, flags ])。這些參數(shù)和regexp_split_to_table的相同。

一些例子:

SELECT foo FROM regexp_split_to_table('the quick brown fox jumps over the lazy dog', '\s+') AS foo;
  foo   
-------
 the    
 quick  
 brown  
 fox    
 jumps 
 over   
 the    
 lazy   
 dog    
(9 rows)

SELECT regexp_split_to_array('the quick brown fox jumps over the lazy dog', '\s+');
              regexp_split_to_array             
-----------------------------------------------
 {the,quick,brown,fox,jumps,over,the,lazy,dog}
(1 row)

SELECT foo FROM regexp_split_to_table('the quick brown fox', '\s*') AS foo;
 foo 
-----
 t         
 h         
 e         
 q         
 u         
 i         
 c         
 k         
 b         
 r         
 o         
 w         
 n         
 f         
 o         
 x         
(16 rows)

正如上一個例子所示,正則表達式分離函數(shù)會忽略零長度的匹配,這種匹配發(fā)生在串的開頭或結尾或者正好發(fā)生在前一個匹配之后。這和正則表達式匹配的嚴格定義是相悖的,后者由regexp_matchregexp_matches實現(xiàn),但是通常前者是實際中最常用的行為。其他軟件系統(tǒng)如Perl也使用相似的定義。

9.7.3.1. 正則表達式細節(jié)

PostgreSQL的正則表達式是使用 Henry Spencer 寫的一個包來實現(xiàn)的。下面的正則表達式的大部分描述都是從他的手冊頁中逐字拷貝過來的。

正則表達式(RE),在POSIX 1003.2 中定義, 它有兩種形式:擴展RE或者是ERE(大概地說就是那些在egrep里的), 基本RE或者是BRE(大概地說就是那些在ed里的)。PostgreSQL支持兩種形式,并且還實現(xiàn)了一些POSIX標準中沒有但是在類似 Perl 或者 Tcl 這樣的語言中得到廣泛應用的一些擴展。使用了那些非POSIX擴展的 RE高級RE, 或者本文檔里說的ARE。ARE 幾乎完全是 ERE 的超集,但是 BRE 有幾個符號上的不兼容(以及更多的限制)。我們首先描述 ARE 和 ERE 形式, 描述那些只適用于 ARE 的特性,然后描述 BRE 的區(qū)別是什么。

注意

PostgreSQL初始時總是推測一個正則表達式遵循 ARE 規(guī)則。但是,可以通過為 RE 模式預置一個embedded option來選擇限制更多的 ERE 或 BRE 規(guī)則,如本文中第 9.7.3.4 節(jié)中所述。這對為期望準確的 POSIX1003.2 規(guī)則的應用提供兼容性很有用。

一個正則表達式被定義為一個或更多分支,它們之間被|分隔。只要能匹配其中一個分支的東西都能匹配正則表達式。

一個分支是一個或多個量化原子或者約束連接而成。一個原子匹配第一個,然后后面的原子匹配第二個, 以此類推;一個空分支匹配空串。

一個量化原子是一個原子, 后面可能跟著一個量詞。沒有量詞的時候,它匹配一個原子, 有量詞的時候,它可以匹配若干個原子。一個原子可以是在表 9.17里面顯示的任何可能。 可能的量詞和它們的含義在表 9.18里顯示。

一個約束匹配一個空串,但只是在滿足特定條件下才匹配。 約束可以在能夠使用原子的地方使用,只是它不能跟著量詞。簡單的約束在表 9.19里顯示; 更多的約束稍后描述。

表 9.17. 正則表達式原子

原子 描述
(re) (其中re是任何正則表達式) 匹配一個對re的匹配,匹配將為可能的報告被記下
(?:re) 同上,但是匹配不會為了報告而被記下 (一個非捕獲圓括號集) (只對 ARE)
. 匹配任意單個字符
[chars] 一個方括號表達式, 匹配chars中的任意一個(詳見第 9.7.3.2 節(jié))
\k (其中k是一個非字母數(shù)字字符) 匹配一個被當作普通字符看待的特定字符, 例如,\\匹配一個反斜線字符
\c 其中c是一個字母數(shù)字 (可能跟著其它字符),它是一個逃逸, 參閱第 9.7.3.3 節(jié)(僅對 ARE; 在 ERE 和 BRE 中,它匹配 c
{ 如果后面跟著一個字符,而不是數(shù)字, 那么就匹配左花括弧{;如果跟著一個數(shù)字, 那么它是range的開始(見下文)
x 其中x是一個沒有其它意義的單個字符,則匹配該字符

RE 不能以反斜線(\)結尾。

注意

如果你關掉了standard_conforming_strings,任何你寫在文字串常量中的反斜線都需要被雙寫。詳見第 4.1.2.1 節(jié)。

表 9.18. 正則表達式量詞

量詞 匹配
* 一個由原子的 0 次或更多次匹配組成的序列
+ 一個由原子的 1 次或更多次匹配組成的序列
? 一個由原子的 0 次或 1 次匹配組成的序列
{m} 一個由原子的正好m次匹配組成的序列
{m,} 一個由原子的m次或更多次匹配組成的序列
{m,n} 一個由原子的從m次到n次(包括)匹配組成的序列;m不能超過n
*? *的非貪婪版本
+? +的非貪婪版本
?? ?的非貪婪版本
{m}? {m}的非貪婪版本
{m,}? {m,}的非貪婪版本
{m,n}? {m,n}的非貪婪版本

使用{...}的形式被稱作范圍。 一個范圍內(nèi)的數(shù)字mn都是無符號十進制整數(shù), 允許的數(shù)值從 0 到 255(包含)。

非貪婪的量詞(只在 ARE 中可用)匹配對應的正常 (貪婪)模式,區(qū)別是它尋找最少的匹配,而不是最多的匹配。詳見第 9.7.3.5 節(jié)。

注意

一個量詞不能緊跟在另外一個量詞后面,例如**是非法的。量詞不能作為表達式或者子表達式的開頭,也不能跟在^或者|后面。

表 9.19. 正則表達式約束

約束 描述
^ 串開頭的匹配
$ 串末尾的匹配
(?=re) 在匹配re的子串開始的任何點的positive lookahead匹配(只對 ARE)
(?!re) 在匹配re的子串開始的任何點的negative lookahead匹配(只對 ARE)
(?<=re) 只要有一個點上有一個子串匹配re端, positive lookbehind就在這個點上匹配(只對 ARE)
(?<!re) 只要有一個點上沒有子串匹配re端, negative lookbehind就在這個點上匹配(只對 ARE)

Lookahead 和 lookbehind 約束不能包含后引用 (參閱第 9.7.3.3 節(jié)),并且其中的所有圓括號 都被認為是非捕獲的。

9.7.3.2. 方括號表達式

方括號表達式是一個包圍在[]中的字符列表。它通常匹配列表中的任意單個字符(但見下文)。 如果列表以^開頭,它匹配任意單個在該列表參與部分中的字符。如果該列表中兩個字符用-隔開, 那它就是那兩個字符(包括在內(nèi))之間的所有字符范圍的縮寫,例如,在 ASCII[0-9]匹配任何十進制數(shù)字。兩個范圍共享一個端點是非法的,例如,a-c-e。范圍與字符集關系密切, 可移植的程序應該避免依靠它們。

想在列表中包含文本],可以讓它做列表的首字符(如果使用了^,需要放在其后)。 想在列表中包含文本-,可以讓它做列表的首字符或者尾字符,或者一個范圍的第二個端點。 想在列表中把文本-當做范圍的起點, 把它用[..]包圍起來,這樣它就成為一個排序元素(見下文)。 除了這些字符本身、一些用[的組合(見下段)以及逃逸(只在 ARE 中有效)以外,所有其它特殊字符 在方括號表達式里都失去它們的特殊含義。特別是,在 ERE 和 BRE 規(guī)則下\不是特殊的, 但在 ARE 里,它是特殊的(引入一個逃逸)。

在一個方括號表達式里,一個排序元素(一個字符、一個被當做一個單一字符排序的多字符序列或者一個表示上面兩種情況的排序序列名稱) 包含在[..]里面的時候表示該排序元素的字符序列。該序列被當做該方括號列表 的一個單一元素。這允許一個包含多字符排序元素的方括號表達式去匹配多于一個字符,例如,如果排序序列包含一個ch排序元素, 那么 RE [[.ch.]]*c匹配chchcc的頭五個字符。

注意

PostgreSQL當前不支持多字符排序元素。這些信息描述了將來可能有的行為。

在方括號表達式里,包圍在[==]里的排序元素是一個等價類, 代表等效于那一個的所有排序元素的字符序列,包括它本身(如果沒有其它等效排序元素,那么就好象封裝定界符是[..])。例如,如果o^是一個等價類的成員,那么[[=o=]]、[[=^=]][o^]都是同義的。一個等價類不能是一個范圍的端點。

在方括號表達式里,在[::]里面封裝的字符類的名字代表屬于該類的所有字符的列表。 字符類不能作為范圍的端點使用。POSIX標準定義了這些字符類的名稱: alnum (字符和數(shù)字), alpha (字符), blank(空格和制表符tab), cntrl (控制符), digit (數(shù)位數(shù)), graph (空格除外可打印字符), lower (小寫字母), print (包含空格可打印字符), punct (標點符號), space(空白), upper (大寫字母), 和 xdigit (十六進制數(shù)). 對于7位ASCII字符集中的字符來說,這些標準字符類的行為在不同平臺上一般是一致的。一個給定的非ASCII字符是否被認為屬于這些類別中的一個,取決于正則表達式函數(shù)或運算符使用的collation(見第 23.2 節(jié)),或者默認情況下取決于數(shù)據(jù)庫的LC_CTYPE locale設置(見第 23.1 節(jié))。非ASCII字符的分類在不同的平臺上會有不同的分類,即使是在類似命名的locale中也是如此。 (但C locale從不認為任何非ASCII字符屬于上述任何一類)。除了這些標準字符類之外, PostgreSQL定義了ascii字符類,它完全包含7位ASCII字符集。

方括號表達式里有兩個特例:方括號表達式[[:<:]][[:>:]]是約束,分別匹配一個單詞開頭和結束的空串。 單詞定義為一個單詞字符序列,前面和后面都沒有其它單詞字符。單詞字符是一個alnum字符(和如上所述POSIX字符類中定義的一樣) 或者一個下劃線。這是一個擴展,兼容 POSIX1003.2, 但那里面并沒有說明, 而且在準備移植到其他系統(tǒng)里去的軟件里一定要小心使用。通常下文描述的約束逃逸更好些(它們并非更標準,但是更容易鍵入)。

9.7.3.3. 正則表達式逃逸

逃逸是以\開頭,后面跟著一個字母數(shù)字字符得特殊序列。 逃逸有好幾種變體:字符項、類縮寫、約束逃逸以及后引用。在 ARE 里, 如果一個\后面跟著一個字母數(shù)字,但是并未組成一個合法的逃逸, 那么它是非法的。在 ERE 中沒有逃逸:在方括號表達式之外,一個后面跟著字母數(shù)字字符的\只是表示該字符是一個普通的字符,而且在一個方括號表達式里, \是一個普通的字符(后者實際上在 ERE 和 ARE 不兼容)。

字符項逃逸用于便于我們在 RE 中聲明那些不可打印的或其他習慣的字符。它們顯示在表 9.20中。

類縮寫逃逸用來提供一些常用的字符類縮寫。它們顯示在表 9.21中。

約束逃逸是一個約束,如果滿足特定的條件,它匹配該空串。它們顯示在表 9.22中。

后引用\n)匹配數(shù)字\n指定的被前面的圓括號子表達式匹配的同一個串 (參閱表 9.23)。例如, ([bc])\1匹配bb或者cc, 但是不匹配bc或者cb。RE 中子表達式必須完全在后引用前面。子表達式以它們的先導圓括號的順序編號。非捕獲圓括號并不定義子表達式。

表 9.20. 正則表達式字符項逃逸

逃逸 描述
\a 警告(響鈴)字符,和 C 中一樣
\b 退格,和 C 中一樣
\B 反斜線(\)的同義詞,用來減少雙寫反斜線
\cX (其中X是任意字符)低序5位和X相同的字符,它的其他位都是零
\e 排序序列名為ESC的字符,如果無法做到該字符為八進制值 033
\f 換頁,和 C 中一樣
\n 新行,和 C 中一樣
\r 回車,和 C 中一樣
\t 水平制表符,和 C 中一樣
\uwxyz (其中wxyz正好是四個十六進制位)十六進制值為0xwxyz的字符
\Ustuvwxyz (其中stuvwxyz正好是八個十六進制位)十六進制值為0xstuvwxyz的字符
\v 垂直制表符,和 C 中一樣
\xhhh (其中hhh是十六進制位的任意序列)十六進制值為0xhhh的字符(一個單一字符,不管用了多少個十六進制位)
\0 值為0(空字節(jié))的字符
\xy (其中xy正好是兩個八進制位,并且不是一個后引用)八進制值為0xy的字符
\xyz (其中xyz正好是三個八進制位,并且不是一個后引用)八進制值為0xyz的字符

十六進制位是0-9、a-fA-F。八進制位是0-7。

指定 ASCII 范圍(0–127)之外的值的數(shù)字字符項轉義的含義取決于數(shù)據(jù)庫編碼。 當編碼是 UTF-8 時,轉義值等價于 Unicode 代碼點,例如 \u1234表示字符U+1234。對于其他多字節(jié)編碼, 字符項轉義通常只是指定該字符的字節(jié)值的串接。如果該轉義值不對應數(shù)據(jù)庫編碼 中的任何合法字符,將不會發(fā)生錯誤,但是它不會匹配任何數(shù)據(jù)。

字符項逃逸總是被當作普通字符。例如,\135是 ASCII 中的], 但\135并不終止一個方括號表達式。

表 9.21. 正則表達式類縮寫逃逸

逃逸 描述
\d [[:digit:]]
\s [[:space:]]
\w [[:alnum:]_](注意下劃線是被包括的)
\D [^[:digit:]]
\S [^[:space:]]
\W [^[:alnum:]_] (注意下劃線是被包括的)

在方括號表達式里,\d、\s\w會失去它們的外層方括號,而\D\S\W是非法的(也就是說,例如[a-c\d]等效于 [a-c[:digit:]]。同樣[a-c\D]等效于 [a-c^[:digit:]]的,也是非法的)。

表 9.22. 正則表達式約束逃逸

逃逸 描述
\A 只在串開頭匹配(與^的不同請參見第 9.7.3.5 節(jié))
\m 只在一個詞的開頭匹配
\M 只在一個詞的末尾匹配
\y 只在一個詞的開頭或末尾匹配
\Y 只在一個詞的不是開頭或末尾的點上匹配
\Z 只在串的末尾匹配(與$的不同請參見第 9.7.3.5 節(jié))

一個詞被定義成在上面[[:<:]][[:>:]]中的聲明。在方括號表達式里,約束逃逸是非法的。

表 9.23. 正則表達式后引用

逃逸 描述
\m (其中m是一個非零位)一個到第m個子表達式的后引用
\mnn (其中m是一個非零位,并且nn是一些更多的位,并且十六進制值mnn不超過目前能看到的封閉捕獲圓括號的數(shù)目)一個到第mnn個子表達式的后引用

注意

在八進制字符項逃逸和后引用之間有一個歷史繼承的歧義存在,這個歧義是 通過下面的啟發(fā)式規(guī)則解決的,像上面描述地那樣。前導零總是表示這是一個八進制逃逸。 而單個非零數(shù)字,如果沒有跟著任何其它位,那么總是被認為后引用。 一個多位的非零開頭的序列也被認為是后引用,只要它出現(xiàn)在合適的子表達式后面 (也就是說,在后引用的合法范圍中的數(shù)),否則就被認為是一個八進制。

9.7.3.4. 正則表達式元語法

除了上面描述的主要語法之外,還有幾種特殊形式和雜項語法。

如果一個 RE 以***:開頭,那么剩下的 RE 都被當作 ARE(這在PostgreSQL中通常是無效的,因為 RE 被假定為 ARE,但是如果 ERE 或 BRE 模式通過flags參數(shù)被指定為一個正則表達式函數(shù)時,它確實能產(chǎn)生效果)。如果一個 RE 以***=開頭, 那么剩下的 RE 被當作一個文本串,所有的字符都被認為是一個普通字符。

一個 ARE 可以以嵌入選項開頭:一個序列(?xyz)(這里的xyz是一個或多個字母字符)聲明影響剩余 RE 的選項。 這些選項覆蓋任何前面判斷的選項 — 特別地,它們可以覆蓋一個正則表達式操作符隱含的大小寫敏感的行為,或者覆蓋 flags 參數(shù)中的正則表達式函數(shù)??捎玫倪x項字母在表 9.24中顯示。注意這些同樣的選項字母也被用在正則表達式函數(shù)的flags參數(shù)中。

表 9.24. ARE 嵌入選項字母

選項 描述
b RE的剩余部分是一個BRE
c 大小寫敏感的匹配(覆蓋操作符類型)
e RE的剩余部分是一個ERE
i 大小寫不敏感的匹配(見第 9.7.3.5 節(jié))(覆蓋操作符類型)
m n的歷史原因的同義詞
n 新行敏感的匹配(見第 9.7.3.5 節(jié))
p 部分新行敏感的匹配(見第 9.7.3.5 節(jié))
q RE的剩余部分是一個文字(quoted)串,全部是普通字符
s 非新行敏感的匹配(默認)
t 緊語法(默認,見下文)
w 逆部分新行敏感(怪異)的匹配(見第 9.7.3.5 節(jié))
x 擴展語法(見下文)

嵌入選項在)終止序列時發(fā)生作用。它們只在 ARE 的開始處起作用 (在任何可能存在的***:控制器后面)。

除了通常的()RE 語法(這種情況下所有字符都有效), 還有一種擴展語法,可以通過聲明嵌入的x選項獲得。在擴展語法里,RE 中的空白字符被忽略,就像那些在#和其后的新行(或 RE 的末尾)之間的字符一樣。這樣就允許我們給一個復雜的 RE 分段和注釋。不過這個基本規(guī)則有三種例外:

  • 空白字符或前置了\#將被保留

  • 方括號表達式里的空白或者#將被保留

  • 在多字符符號里面不能出現(xiàn)空白和注釋,例如(?:

為了這個目的,空白是空格、制表符、新行和任何屬于空白字符類的字符。

最后,在 ARE 里,方括號表達式外面,序列(?#ttt)(其中ttt是任意不包含一個))的文本)是一個注釋, 它被完全忽略。同樣,這樣的東西是不允許出現(xiàn)在多字符符號的字符中間的,例如 (?:。這種注釋更像是一種歷史產(chǎn)物而不是一種有用的設施,并且它們的使用已經(jīng)被廢棄;請使用擴展語法來替代。

如果聲明了一個初始的***=控制器,那么所有這些元語法擴展都不能使用,因為這樣表示把用戶輸入當作一個文字串而不是 RE 對待。

9.7.3.5. 正則表達式匹配規(guī)則

在 RE 可以在給定串中匹配多于一個子串的情況下, RE 匹配串中最靠前的那個子串。如果 RE 可以匹配在那個位置開始 的多個子串,要么是取最長的子串,要么是最短的,具體哪種, 取決于 RE 是貪婪的還是非貪婪的。

一個 RE 是否貪婪取決于下面規(guī)則:

  • 大多數(shù)原子以及所有約束,都沒有貪婪屬性(因為它們畢竟無法匹配個數(shù)變化的文本)。

  • 在一個 RE 周圍加上圓括號并不會改變其貪婪性。

  • 帶一個固定重復次數(shù)量詞 ({m}或者{m}?) 的量化原子和原子自身具有同樣的貪婪性(可能是沒有)。

  • 一個帶其他普通的量詞(包括{m,n}m等于n的情況)的量化原子是貪婪的(首選最長匹配)。

  • 一個帶非貪婪量詞(包括{m,n}?m等于 n的情況)的量化原子是非貪婪的(首選最短匹配)。

  • 一個分支 — 也就是說,一個沒有頂級|操作符的 RE — 和它里面的第一個有貪婪屬性的量化原子有著同樣的貪婪性。

  • 一個由|操作符連接起來的兩個或者更多分支組成的 RE 總是貪婪的。

上面的規(guī)則所描述的貪婪屬性不僅僅適用于獨立的量化原子, 而且也適用于包含量化原子的分支和整個 RE。這里的意思是, 匹配是按照分支或者整個 RE 作為一個整體匹配最長或者最短的可能子串。 一旦整個匹配的長度確定,那么匹配任意特定子表達式的部分就基于該子表達式的貪婪屬性進行判斷,在 RE 里面靠前的子表達式的優(yōu)先級高于靠后的子表達式。

一個相應的例子:

SELECT SUBSTRING('XY1234Z', 'Y*([0-9]{1,3})');
結果:123
SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
結果:1

在第一個例子里,RE 作為整體是貪婪的,因為Y*是貪婪的。它可以匹配從Y開始的東西,并且它匹配從這個位置開始的最長的串, 也就是,Y123。輸出是這里的圓括號包圍的部分,或者說是123。在第二個例子里, RE 總體上是一個非貪婪的 RE,因為Y*?是非貪婪的。它可以匹配從 Y開始的最短的子串,也就是說Y1。子表達式[0-9]{1,3}是貪婪的,但是它不能修改總體匹配長度的決定; 因此它被迫只匹配1。

簡而言之,如果一個 RE 同時包含貪婪和非貪婪的子表達式,那么總的匹配長度要么是盡可能長,要么是盡可能短,這取決于給整個 RE 賦予的屬性。給子表達式賦予的屬性只影響在這個匹配里,各個子表達式之間相互允許吃掉的多少。

量詞{1,1}{1,1}?可以分別用于在一個子表達式 或者整個 RE 上強制貪婪或者非貪婪。當需要整個 RE 具有不同于從其元素中 推導出的貪婪屬性時,這很有用。例如,假設我們嘗試將一個包含一些數(shù)字的 字符串分隔成數(shù)字以及在它們之前和之后的部分,我們可能會嘗試這樣做:

SELECT regexp_match('abc01234xyz', '(.*)(\d+)(.*)');
Result: {abc0123,4,xyz}

這不會有用:第一個.*是貪婪的,因此它會吃掉盡可能多的字符而留下\d+去匹配在最后一個可能位置上的最 后一個數(shù)字。我們可能會通過讓它變成非貪婪來修復:

SELECT regexp_match('abc01234xyz', '(.*?)(\d+)(.*)');
Result: {abc,0,""}

這也不會有用:因為現(xiàn)在 RE 作為整體來說是非貪婪的,因此它會盡快結束 全部的匹配。我們可以通過強制 RE 整體是貪婪的來得到我們想要的:

SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
Result: {abc,01234,xyz}

獨立于 RE 的組件的貪婪性之外控制 RE 的整體貪婪性為處理變長模式提供了 很大的靈活性。

在決定更長或者更短的匹配時,匹配長度是以字符衡量的,而不是排序元素。一個空串會被認為比什么都不匹配長。例如:bb*匹配abbbc的中間三個字符;(week|wee)(night|knights)匹配weeknights的所有十個字符; 而(.*).*匹配 abc的時候,圓括號包圍的子表達式匹配所有三個字符;當(a*)*被拿來匹配bc時,整個 RE 和圓括號 子表達式都匹配一個空串。

如果聲明了大小寫無關的匹配,那么效果就好像所有大小寫區(qū)別在字母表中消失了。如果在多個情況中一個字母以一個普通字符的形式出現(xiàn)在方括號表達式外面,那么它實際上被轉換成 一個包含大小寫的方括號表達式,也就是說,x 變成 [xX]。 如果它出現(xiàn)在一個方括號表達式里面,那么它的所有大小寫的同族都被加入 方括號表達式中,也就是說,x變成[xX]。當它出現(xiàn)在一個方括號表達式內(nèi)時,它的所有大小寫副本都被加入到方括號表達式中,例如, [x]會變成[xX],而[^x]會變成[^xX]。

如果指定了新行敏感的匹配,.和使用^的方括號表達式 將永遠不會匹配新行字符(這樣,匹配就絕對不會跨越新行,除非 RE 顯式地安排了這樣的情況)并且^$除了分別匹配串開頭和結尾之外,還將分別匹配新行后面和前面的空串。但是 ARE 逃逸\A\Z仍然匹配串的開頭和結尾。

如果指定了部分新行敏感的匹配,那么它影響.和方括號表達式, 這個時候和新行敏感的匹配一樣,但是不影響^$。

如果指定了逆新行敏感匹配,那么它影響^$,其作用和在新行敏感的匹配里一樣,但是不影響.和方括號表達式。這個并不是很有用,只是為了滿足對稱性而提供的。

9.7.3.6. 限制和兼容性

在這個實現(xiàn)里,對 RE 的長度沒有特別的限制。但是,那些希望高移植性的程序應該避免使用長度超過 256 字節(jié)的 RE,因為 POSIX 兼容 的實現(xiàn)可以拒絕接受這樣的 RE。

ARE 實際上和 POSIX ERE 不兼容的唯一的特性是在方括號表達式里\并不失去它特殊的含義。所有其它 ARE 特性都使用在 POSIX ERE 里面是非法或者是未定義、未聲明效果的語法;指示器的***就是在 POSIX 的 BRE 和 ERE 之外的語法。

許多 ARE 擴展都是從 Perl 那里借來的(但是有些被做了修改來清理它們),以及一些 Perl 里沒有出現(xiàn)的擴展。要注意的不兼容性包括\b、\B、對結尾的新行缺乏特別的處理、對那些被新行敏感匹配的東西附加的補齊方括號表達式、在 lookahead/lookbehind 約束里對圓括號和后引用的限制以及最長/最短 匹配(而不是第一匹配)的語義。

PostgreSQL 7.4 之前的版本中識別的 ARE 和 ERE 語法存在兩個非常明顯的不兼容:

  • 在 ARE 中,后面跟著一個字母數(shù)字字符的\要么是一個逃逸要么是一個錯誤, 但是在以前的版本里,它只是寫該字母數(shù)字字符的另外一種方法。這個應該不是什么問題, 因為在以前的版本里沒有什么理由會讓我們寫這樣的序列。

  • 在 ARE 里,\[]里還是一個特殊字符, 因此在方括號表達式里的一個文本\必須被寫成\\。

9.7.3.7. 基本正則表達式

BREs 在幾個方面和 ERE 不太一樣。在 BRE 中,|、+?都是普通字符并且沒有與它們功能等價的東西。范圍的定界符是\{\}, 因為 {}本身是普通字符。嵌套的子表達式的圓括號是\(\),因為()自身是普通字符。除非在 RE 開頭或者是圓括號子表達式開頭,^都是一個普通字符。 除非在 RE 結尾或者是圓括號子表達式的結尾, $是一個普通字符。如果*出現(xiàn)在 RE 開頭或者是圓括號封裝的子表達式開頭 (前面可能有^),那么它是個普通字符。最后,可以用單數(shù)字的后引用,\<\>分別是[[:<:]][[:>:]]的同義詞;在 BRE 中沒有其它可用的逃逸。

9.7.3.8. 與XQuery的區(qū)別 (LIKE_REGEX)

從SQL:2008開始,SQL標準中包含了一個LIKE_REGEX操作符,它根據(jù)XQuery正則表達式標準執(zhí)行模式匹配。 PostgreSQL還沒有實現(xiàn)這個操作符,但是你可以使用regexp_match()函數(shù)獲得非常類似的行為,因為XQuery正則表達式非常接近于上面描述的ARE語法。

與現(xiàn)有的基于POSIX的 正則表達式功能和XQuery正則表達式包括。

  • 不支持XQuery字符類減法。 這個功能的一個例子是使用下面的例子,只匹配英文輔音。[a-z-[aeiou]]。

  • XQuery字符類速記c。 不支持C、iI。

  • 不支持使用p{UnicodeProperty}或反過來的P{UnicodeProperty}的XQuery字符類元素。

  • POSIX根據(jù)當前的locale來解釋字符類,如/w (見表 9.21)(你可以通過在操作符或函數(shù)中附加一個COLLATE子句來控制)。 XQuery通過引用Unicode字符屬性來指定這些類,因此只有遵循Unicode規(guī)則的locale才能獲得等效的行為。

  • SQL標準(而不是XQuery本身)試圖滿足更多的需求。newline的變體比POSIX的變體。 上面描述的對新行敏感的匹配選項只考慮ASCII NL (n)是新行,但SQL會讓我們把CR (r)、CRLF (r/n)(Windows風格的新行),以及一些Unicode唯一的字符,如LINE SEPARATOR (U+2028)也視為新行。值得注意的是,.s應該算作一個字符,而不是按照SQL的規(guī)定算作兩個字符。

  • 在表 9.20中描述的字符輸入轉義中,XQuery只支持n、rt。

  • XQuery不支持[::name :]]語法,不支持括號表達式中的字符類。

  • XQuery沒有 lookahead 或 lookbehind 約束,也沒有任何在 表 9.22.

  • 第 9.7.3.4 節(jié)中描述的metasyntax形式在XQuery中不存在。

  • 由XQuery定義的正則表達式標志字母與POSIX的選項字母相關,但不一樣。 表 9.24)。 雖然iq選項的行為是一樣的,但其他的選項卻不一樣。

    • XQuery的s(允許點匹配換行)和m(允許^$在換行處匹配)標志提供了與POSIX的n相同的行為。pw標志,但它們與POSIX的 sm標志的行為不匹配。特別要注意的是,點匹配-newline是POSIX中的默認行為,但不是XQuery。

    • XQuery的x(忽略模式中的空格)標志與POSIX的擴展模式標志明顯不同。 POSIX的x標志也允許#在模式中開始注釋,并且POSIX不會忽略反斜線后的空格字符。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號