6.6. パターンマッチング

PostgreSQL には、パターンマッチングを行うに際して 3 つの異なった手法があります。 伝統的な SQLLIKE 演算子、これより新しい SQL99SIMILAR TO 演算子、および POSIX 型の正規表現です。 更に、SQL99 型もしくは POSIX 型正規表現を使って、パターンマッチング関数 SUBSTRING を使用することも可能です。

Tip: 上記の手法では検索できないようなパターンマッチングが必要な場合は、Perl もしくは Tcl でユーザ定義関数を作成することを検討してください。

6.6.1. LIKE

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

それぞれの pattern は文字列の、ある集合を定義します。LIKE 式は pattern によって示される文字列の集合に string が含まれていれば真を返します。(想像されるとおり、NOT LIKE 式は LIKE 式が真を返す場合には偽を返し、その逆もまた同じです。同等の式として NOT (string LIKE pattern) とも表現できます。)

pattern がパーセント記号 (%) もしくはアンダースコア (_) を含んでいない場合 pattern は自身の文字列そのものです。 この場合 LIKE 式は等号演算子のように振舞います。pattern の中にあるアンダースコア (_) はすべての一文字とのマッチを意味し、パーセント記号 (%) は 0 文字以上の文字列とのマッチを意味します。

以下にいくつか例を示します。

'abc' LIKE 'abc'    
'abc' LIKE 'a%'     
'abc' LIKE '_b_'    
'abc' LIKE 'c'      

LIKE によるパターンマッチは常に文字列全体に対して行われます。ですから、文字列内のいかなる位置に於いてもパターンマッチさせるにはパーセント記号を始めと終りに附ける必要があります。

リテラルのアンダースコアやパーセント記号を他の文字のマッチングに使用するのではなくそのものをマッチさせたい場合には、pattern の中のそれぞれのアンダースコアとパーセント記号はエスケープ文字でエスケープされなければなりません。デフォルトのエスケープ文字は逆スラッシュですが、ESCAPE 句で他の文字を指定することがでいます。エスケープ文字そのものをマッチさせるにはエスケープ文字をふたつ書きます。

リテラル文字列において逆スラッシュには始めから特別な意味合いがあるので逆スラッシュを含んだパターン定数を記述するときは問い合わせの中で 2 つの逆スラッシュを記述する必要があることに注意してください。 したがって、実際にリテラルのバックスラッシュにマッチするパターンを記述するには、問い合わせの中でバックスラッシュを 4 つ記述する必要があります。ESCAPE 句で他のエスケープ文字を選択すればこのような状況を回避でき、逆スラッシュは LIKE 式にとり特殊な文字ではなくなります。(とはいっても、リテラル文字列パーサにとっては依然として特殊文字なので、やはり 2 つは必要です。

同時に ESCAPE '' と記述することでエスケープ文字を選択しないことも可能です。 これにより、事実上エスケープのメカニズムが働かなくなります。これにより、パターン内のアンダースコアおよびパーセント記号の特別な意味を解除することはできなくなります。

現在動いているロケールに従って大文字小文字を区別しないマッチを行うのであれば LIKE の替わりに ILIKE キーワードを使うことができます。これは SQL 標準ではありませんが PostgreSQL の拡張です。

~~ 演算子は LIKE 式と等価で、~~*ILIKE に対応します。また NOT LIKE および NOT ILIKE を表す !~~ および !~~* 演算子があります。これらすべての演算子は PostgreSQL 固有のものです。

6.6.2. SIMILAR TO および SQL99 の正規表現

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

SIMILAR TO 演算子は、そのパターンが、与えられた文字列にマッチするかどうかにより、真 (true) もしくは偽 (false) を返します。 これは、SQL99 の正規表現定義を使用してパターンを解釈するという点以外は、LIKE に非常によく似ています。 SQL99 の正規表現は、LIKE 表記と一般的な正規表現の表記とを混ぜ合わせたようなものになっています。

LIKE と同様、SIMILAR TO 演算子は、そのパターンが文字列全体にマッチした場合のみ処理を行ないます。これは、パターンが文字列の一部分であってもマッチする、一般的な正規表現の処理とは異なっています。 同じく、LIKE と同様、SIMILAR TO では、% および _ を、それぞれ任意の文字列および任意の単一文字を意味するワイルドカード文字として使用します (これらは、POSIX 正規表現での .* および . に相当します)。

LIKE から取り入れた上記の機能に加え、SIMILAR TO では、以下のように POSIX 正規表現から取り入れたパターンマッチングメタキャラクターもサポートしています。

バウンド反復 (? および {...}) は、POSIX にはありますが、ここでは使用できないことに注意してください。 また、ドット (.) はメタキャラクターではありません。

LIKE と同様、バックスラッシュは任意のメタキャラクターの特殊な意味を無効にします。または、異なるエスケープ文字を ESCAPE で指定することが可能です。

以下にいくつか例を示します。

'abc' SIMILAR TO 'abc'      
'abc' SIMILAR TO 'a'        
'abc' SIMILAR TO '%(b|d)%'  
'abc' SIMILAR TO '(b|c)%'   

3 つのパラメータを持つ SUBSTRING 関数、SUBSTRING(string FROM pattern FOR escape) を使用して、SQL99 正規表現パターンにマッチする部分文字列を取り出すことができます。 SIMILAR TO と同様、指定したパターンがデータ文字列全体にマッチする必要があります。マッチしない場合、関数は終了し、NULL を返します。 マッチした場合に返されるべきパターンの一部を示すために、SQL99 では、エスケープ文字の後に二重引用符 (") をつなげたものがパターンに 2 つ含まれる必要があると指定します。 これらのマーカーで囲われたパターンの一部にマッチするテキストが返されます。

以下にいくつか例を示します。

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 による正規表現は、パターンマッチングという意味合いでは、LIKE および SIMILAR TO 演算子よりもさらに有力です。egrepsed、あるいは awk のような多くの Unix ツールはここで解説しているのと類似したパターンマッチング言語を使用しています。

正規表現とは文字列の集合(正規集合)の簡略された定義である文字が連なっているものです。ある文字列が正規表現で記述された正規集合の要素になっていれば、その文字列は正規表現にマッチしていると呼ばれます。LIKE 関数を使った時と同じように、正規表現言語で特殊文字とされているもの以外、パターン文字は文字列と完全にマッチされます。 とはいっても正規表現は LIKE 関数が使用するものと異なる特殊文字を使用します。LIKE 関数のパターンと違って正規表現は、明示的に正規表現が文字列の最初または最後からと位置指定 (anchoring) されていない限り文字列内のどの位置でもマッチを行えます。

以下にいくつか例を示します。

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

2 つのパラメータを持つ SUBSTRING 関数、SUBSTRING(string FROM pattern) を使用して、POSIX 正規表現パターンにマッチする部分文字列を取り出すことができます。 この関数は、マッチするものがない場合には NULL を返し、ある場合はパターンにマッチしたテキストの一部を返します。 しかし、任意の丸括弧を有するパターンの場合、最初の括弧内部分正規表現 (左括弧が最初に来るもの) にマッチするテキストの一部が返されます。 この例外をトリガせずにその中に丸括弧を使用する場合、常にすべての表現を丸括弧で囲むことができます。

以下にいくつか例を示します。

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

POSIX 1003.2 の定義によると、正規表現 (RE) には 2 つの形式があるとされます。 最新の (modern) 正規表現 (大まかにいって egrep に代表されるもので、1003.2 では 「拡張 (extended)」正規表現と呼ばれるもの)、および、使われなくなった正規表現 (大まかには ed に代表されるもので、1003.2 では「ベーシック (basic)」正規表現と呼ばれるもの) の二つです。PostgreSQL は最新の形式を実装しています。

最新の正規表現はひとつまたはそれ以上の | で区切られた空でないブランチです。ブランチの一つとでもマッチすればマッチしたことになります。

ブランチはひとつまたはそれ以上の ピース の連結です。最初のピースからその次のピースへのマッチというふうにマッチされます。

ピースとは原子で、*+?、あるいは バウンドが後に続く可能性があります。* が後に続く原子は 0 回以上連続して原子とのマッチを行うマッチです。+ が後に続く原子は 1 回もしくはそれ以上連続して原子とのマッチを行うマッチです。? が後に続く原子は 0 または 1 回原子とマッチを行います。

バウンドは、{ で始まり、その後に符号無し 10 進数整数が続き、その後にたぶん , が続き、そしてその後にこれもたぶんもう一つ符号無し 10 進数整数が続いて、最後は必ず } で括られます。整数は 0 から RE_DUP_MAX (255) までの範囲であって、2 つ存在する場合最初の整数は 2 番目の整数より大きくあってはいけません。1 つの整数 i があってコンマを持たないバウンドが続く原子は正確に i 回連続して原子とマッチを行います。1 つの整数 i とコンマが付帯するバウンドが続く原子は i 回以上連続して原子とマッチを行います。2 つの整数 ij を持ったバウンドが続く原子は i から j 回(i と j を含んだ間)連続して原子とマッチを行います。

Note: 反復演算子 (?*+、もしくはバウンド)は他の反復演算子と併用することはできません。反復演算子は式または副式を開始させることや、^ あるいは | の後に続くことはできません。

原子は(正規表現とマッチするマッチングを実行する) () で括られた正規表現、(NULL 列とマッチングを実行する) () の空集合、括弧式(後述)、(いかなる 1 文字ともマッチングを実行する) .、(入力文字列の先頭にある NULL 文字列とマッチングを実行する) ^、(入力文字列の後尾のヌル文字列とマッチングを実行する) $ 、(本来の文字としてその文字がマッチングされる)文字 ^.[$()|*+?{\ の前に置かれる \、(あたかも \ が存在していなかったかのように、その文字が本来の意味を持つ文字としてマッチングされる)それら以外の文字が次にくる \、もしくは { の直後に数字以外の文字が続いたものは通常の文字で、バウンドの開始ではありません。\ で正規表現を終了させることは禁じられています。

逆スラッシュ (\) は元もとリテラル文字列において特別の意味が有ることに注意してください。 ですから、逆スラッシュがあるパターン定数を記述するには問い合わせの中でふたつの逆スラッシュを記述しなければなりません。

角括弧内正規表現 (bracket expression) とは [] で括られた文字のリストです。通常リストの中のいずれの一文字ともマッチを行います(以下も参照)。リストが ^ で始まっている場合はリストに有る残りの文字ではなくどんな一文字ともマッチを行います(以下も参照)。リストの中で二つの文字が - で分割されている場合、その文字を含んだ連鎖している繋がりの文字範囲を示す省略形です。 例えば、ASCII[0-9] はすべての 10 進数とマッチを実行します。a-c-e のように二つの終りの位置の範囲を指定することはできません。範囲はかなり連鎖の順番に依存しますので、移植性が求められるプログラムでは使用を避けてください。

リストにリテラル ] を含める場合は、(^ が存在すればその直後に)最初の文字とします。リストにリテラル - を含めたい場合は、最初の文字か最後の文字、もしくは範囲の終了とします。リテラル - を範囲の開始としたい場合には照合要素 (collating element、下記参照)とするために [..] で括ってください。これらの例外と [(次の段落を参照)の組み合わせ方の幾つかを除き、\ を含むすべての特別な意味を持つ文字はその特殊性を角括弧内正規表現内で失います。

角括弧内正規表現内で、[..] で囲まれた照合要素は(あたかも一文字か照合順序名であるかのように照合を行う一文字、または複数の文字列)その照合要素の文字列を意味します。文字列は角括弧内正規表現リストの単一の要素です。ですから、複数文字による照合要素のある角括弧内正規表現は一文字以上とマッチします。 例えば、照合順序に照合要素 ch があると正規表現 [[.ch.]]*cchchcc 文字列の最初の 5 文字とマッチを行います。

角括弧内正規表現内で、[==] で囲まれた照合要素は等価クラスで、それ自身を含め、それと等価なすべての照合要素の文字列を意味します。(等価照合要素がない場合、囲んでいる角括弧の区切り文字は単なる [..] として扱われます。)例えば、o^ が等価クラスの構成要素の場合、[[=o=]][[=^=]]、および [o^] はすべて同義です。等価クラスは範囲の終端指定にはできません。

角括弧内正規表現内で、[::]に囲まれている文字クラスの名称はそのクラスに属する文字すべてのリストを意味します。標準文字クラス名には以下のようなものがあります。alnumalphablankcntrldigitgraphlowerprintpunctspaceupperxdigit。これらは ctype で定義されている文字クラスを意味します。ロケールによりこれ以外のものが提供されることもあります。文字クラスは範囲の終端指定にはできません。

角括弧内正規表現には 2 つの特殊事例があります。 角括弧内正規表現 [[:<:]] および [[:>:]]はそれぞれ単語の先頭と末尾のヌル文字列にマッチします。単語とはその前後に単語文字が付かない単語文字列と定義されます。単語文字とは ( ctype で定義されている) 英数字またはアンダスコアです。これは拡張で、互換性を持っていますが POSIX 1003.2 で指定されていません。 他のシステムに移植予定のソフトウェアなどには注意をして使用してください。

正規表現が文字列の中の 1 つ以上の部分文字列とマッチする場合に於いて正規表現は最初にマッチした部分文字列とマッチします。その位置からまた 1 つ以上の部分文字列とマッチした際は、正規表現は最も長い部分文字列とマッチします。部分正規表現は、正規表現において先に開始した部分正規表現が後から開始された部分正規表現より優先するため、すべてのマッチは可能な限り長くても良いという制約に従う限りに於いて可能な限り長い部分正規表現ともマッチします。従って、より高いレベルの部分正規表現はより低いレベルの構成要素を有する部分正規表現より優先します。

マッチの長さは照合要素ではなく文字列で測られます。ヌル文字列は全くマッチする要素がない文字列よりも長いと考えられます。例えば、bb*abbbc の真中の 3 文字とマッチし、(wee|week)(knights|nights)weeknights のすべての 10 文字とマッチし、abc に対して (.*).* がマッチされると、丸括弧内部分正規表現は 3 つの文字すべてにマッチし、bc に対して (a*)* がマッチされると、全体の正規表現と丸括弧内正規表現はヌル文字列にマッチします。

もし大文字小文字を区別しないマッチングが指定されると、アルファベット文字の大文字小文字の区別が全くなくなったと同じ効果をあたえます。角括弧内正規表現の外側にアルファベットの大文字小文字が混ざった通常の文字が出てきた場合、例えば、x[xX] となるように大文字小文字ともに角括弧内正規表現に実質的に転換されます。角括弧正規表現の中に現われたときは、(例えば、) [x][xX] となり、また [^x][^xX] となるように、すべての大文字小文字それぞれの対が追加されます。

メモリーによる制限を除けば、正規表現の長さに特定の制限はありません。メモリーの使用はほぼ正規表現の大きさに線形比例し、バウンド反復を 除いて正規表現の複雑さにはほぼ無関係です。バウンド反復はマクロ拡張で実装されていて、反復回数が多かったり、バウンド反復が入れ子になっていると処理時間とメモリー空間を犠牲にします。例えば、((((a{1,100}){1,100}){1,100}){1,100}){1,100} のような正規表現は(つまるところ)現存するほとんどのマシンでスワップ領域を使い切ってしまいます。 [1]

Notes

[1]

ここは 1994 年に書かれたものです。 気になりませんか。数値はたぶん変わっているでしょうが問題は存続しています。