使用正規表達式編寫更好的 SQL

使用正規表達式編寫更好的 SQL

http://www.oracle.com/technology/oramag/webcolumns/2003/techarticles/rischert_regexp_pt2.html#t2

http://www.oracle.com/technology/global/cn/oramag/webcolumns/2003/techarticles/rischert_regexp_pt1.html

作者:Alice Rischert

Oracle Database 10g 中的正規表達式特性是一個用於處理文本數據的強大工具

Oracle Database 10g 的一個新特性大大提高了您搜索和處理字符數據的能力。這個特性就是正規表達式,是一種用來描述文本模式的表示方法。很久以來它已在許多編程語言和大量 UNIX 實用工具中出現過了。

Oracle 的正規表達式的實施是以各種 SQL 函數和一個 WHERE 子句操作符的形式出現的。如果您不熟悉正規表達式,那麼這篇文章可以讓您瞭解一下這種新的極其強大然而表面上有點神祕的功能。已經對正規表達式很熟悉的讀者可以瞭解如何在 Oracle SQL 語言的環境中應用這種功能。

什麼是正規表達式?

正規表達式由一個或多個字符型文字和/或元字符組成。在最簡單的格式下,正規表達式僅由字符文字組成,如正規表達式 cat。它被讀作字母 c,接着是字母 at,這種模式匹配 cat、locationcatalog 之類的字符串。元字符提供算法來確定 Oracle 如何處理組成一個正規表達式的字符。當您瞭解了各種元字符的含義時,您將體會到正規表達式用於查找和替換特定的文本數據是非常強大的。

驗證數據、識別重複關鍵字的出現、檢測不必要的空格,或分析字符串只是正規表達式的許多應用中的一部分。您可以用它們來驗證電話號碼、郵政編碼、電子郵件地址、社會安全號碼、IP 地址、文件名和路徑名等的格式。此外,您可以查找如 HTML 標記、數字、日期之類的模式,或任意文本數據中符合任意模式的任何事物,並用其它的模式來替換它們。

用 Oracle Database 10g 使用正規表達式

您可以使用最新引進的 Oracle SQL REGEXP_LIKE 操作符和 REGEXP_INSTR、REGEXP_SUBSTR 以及 REGEXP_REPLACE 函數來發揮正規表達式的作用。您將體會到這個新的功能如何對 LIKE 操作符和 INSTR、SUBSTRREPLACE 函數進行了補充。實際上,它們類似於已有的操作符,但現在增加了強大的模式匹配功能。被搜索的數據可以是簡單的字符串或是存儲在數據庫字符列中的大量文本。正規表達式讓您能夠以一種您以前從未想過的方式來搜索、替換和驗證數據,並提供高度的靈活性。

正規表達式的基本例子

在使用這個新功能之前,您需要了解一些元字符的含義。句號 (.) 匹配一個正規表達式中的任意字符(除了換行符)。例如,正規表達式 a.b 匹配的字符串中首先包含字母 a,接着是其它任意單個字符(除了換行符),再接着是字母 b。字符串 axb、xaybxabba 都與之匹配,因爲在字符串中隱藏了這種模式。如果您想要精確地匹配以 a 開頭和以 b 結尾的一條三個字母的字符串,則您必須對正規表達式進行定位。脫字符號 (^) 元字符指示一行的開始,而美元符號 ($) 指示一行的結尾(參見表 1)。因此, 正規表達式 ^a.b$ 匹配字符串 aab、abbaxb。將這種方式與 LIKE ²Ù×÷·û提供的類似的模式匹配 a_b 相比較,其中 (_) 是單字符通配符。

默認情況下,一個正規表達式中的一個單獨的字符或字符列表只匹配一次。爲了指示在一個正規表達式中多次出現的一個字符,您可以使用一個量詞,它也被稱爲重複操作符。.如果您想要得到從字母 a 開始並以字母 b 結束的匹配模式,則您的正規表達式看起來像這樣:^a.*b$* 元字符重複前面的元字符 (.) 指示的匹配零次、一次或更多次。LIKE 操作符的等價的模式是 a%b,其中用百分號 (%) 來指示任意字符出現零次、一次或多次。

表 2 給出了重複操作符的完整列表。注意它包含了特殊的重複選項,它們實現了比現有的 LIKE 通配符更大的靈活性。如果您用圓括號括住一個表達式,這將有效地創建一個可以重複一定次數的子表達式。例如,正規表達式 b(an)*a 匹配 ba、bana、banana、yourbananasplit 等。

Oracle 的正規表達式實施支持 POSIX (可移植操作系統接口)字符類,參見表 3 中列出的內容。這意味着您要查找的字符類型可以非常特別。假設您要編寫一條僅查找非字母字符的 LIKE 條件 — 作爲結果的 WHERE 子句可能不經意就會變得非常複雜。

POSIX 字符類必須包含在一個由方括號 ([]) 指示的字符列表中。例如,正規表達式 [[:lower:]] 匹配一個小寫字母字符,而 [[:lower:]]{5} 匹配五個連續的小寫字母字符。

除 POSIX 字符類之外,您可以將單獨的字符放在一個字符列表中。例如,正規表達式 ^ab[cd]ef$ 匹配字符串 abcefabdef。必須選擇 cd

除脫字符 (^) 和連字符 (-) 之外,字符列表中的大多數元字符被認爲是文字。正規表達式看起來很複雜,這是因爲一些元字符具有隨上下文環境而定的多重含義。^ 就是這樣一種元字符。如果您用它作爲一個字符列表的第一個字符,它代表一個字符列表的非。因此,[^[:digit:]] 查找包含了任意非數字字符的模式,而 ^[[:digit:]] 查找以數字開始的匹配模式。連字符 (-) 指示一個範圍,正規表達式 [a-m] 匹配字母 a 到字母 m 之間的任意字母。但如果它是一個字符行中的第一個字符(如在 [-afg] 中),則它就代表連字符。

之前的一個例子介紹了使用圓括號來創建一個子表達式;它們允許您通過輸入更替元字符來輸入可更替的選項,這些元字符由豎線 (|) 分開。

例如,正規表達式 t(a|e|i)n 允許字母 tn 之間的三種可能的字符更替。匹配模式包括如 tan、ten、tinPakistan 之類的字,但不包括 teen、mountaintune。作爲另一種選擇,正規表達式 t(a|e|i)n 也可以表示爲一個字符列表 t[aei]n表 4 彙總了這些元字符。雖然存在更多的元字符,但這個簡明的概述足夠用來理解這篇文章使用的正規表達式。

REGEXP_LIKE 操作符

REGEXP_LIKE 操作符向您介紹在 Oracle 數據庫中使用時的正規表達式功能。表 5 列出了 REGEXP_LIKE 的語法。

下面的 SQL 查詢的 WHERE 子句顯示了 REGEXP_LIKE 操作符,它在 ZIP 列中搜索滿足正規表達式 [^[:digit:]] 的模式。它將檢索 ZIPCODE 表中的那些 ZIP 列值包含了任意非數字字符的行。

SELECT zip
FROM zipcode
WHERE REGEXP_LIKE(zip, '[^[:digit:]]')
ZIP
-----
ab123
123xy
007ab
abcxy

這個正規表達式的例子僅由元字符組成,更具體來講是被冒號和方括號分隔的 POSIX 字符類 digit。第二組方括號(如 [^[:digit:]] 中所示)包括了一個字符類列表。如前文所述,需要這樣做是因爲您只可以將 POSIX 字符類用於構建一個字符列表。

REGEXP_INSTR 函數

這個函數返回一個模式的起始位置,因此它的功能非常類似於 INSTR 函數。新的 REGEXP_INSTR 函數的語法在表 6 中給出。這兩個函數之間的主要區別是,REGEXP_INSTR 讓您指定一種模式,而不是一個特定的搜索字符串;因而它提供了更多的功能。接下來的示例使用 REGEXP_INSTR 來返回字符串 Joe Smith, 10045 Berry Lane, San Joseph, CA 91234 中的五位郵政編碼模式的起始位置。如果正規表達式被寫爲 [[:digit:]]{5},則您將得到門牌號的起始位置而不是郵政編碼的,因爲 10045 是第一次出現五個連續數字。因此,您必須將表達式定位到該行的末尾,正如 $ 元字符所示,該函數將顯示郵政編碼的起始位置,而不管門牌號的數字個數。

SELECT REGEXP_INSTR('Joe Smith, 10045 Berry Lane, San Joseph, CA 91234',
'[[:digit:]]{5}$')
AS rx_instr
FROM dual
RX_INSTR
----------
45

編寫更復雜的模式

讓我們在前一個例子的郵政編碼模式上展開,以便包含一個可選的四位數字模式。您的模式現在可能看起來像這樣:[[:digit:]]{5}(-[[:digit:]]{4})?$。如果您的源字符串以 5 位郵政編碼或 5 位 + 4 位郵政編碼的格式結束,則您將能夠顯示該模式的起始位置。

SELECT REGEXP_INSTR('Joe Smith, 10045 Berry Lane, San Joseph, CA 91234-1234',
' [[:digit:]]{5}(-[[:digit:]]{4})?$')
AS starts_at
FROM dual
STARTS_AT
----------
44

在這個示例中,括弧裏的子表達式 (-[[:digit:]]{4}) 將按 ? 重複操作符的指示重複零次或一次。此外,企圖用傳統的 SQL 函數來實現相同的結果甚至對 SQL 專家也是一個挑戰。爲了更好地說明這個正規表達式示例的不同組成部分,表 7 包含了一個對單個文字和元字符的描述。

REGEXP_SUBSTR 函數

·Ç³£ÀàËÆÓÚ SUBSTR 函數的 REGEXP_SUBSTR 函數用來提取一個字符串的一部分。表 8 顯示了這個新函數的語法。在下面的示例中,匹配模式 [^,]* 的字符串將被返回。該正規表達式搜索其後緊跟着空格的一個逗號;然後按 [^,]* 的指示搜索零個或更多個不是逗號的字符,最後查找另一個逗號。這種模式看起來有點像一個用逗號分隔的值字符串。

SELECT REGEXP_SUBSTR('first field, second field , third field',
', [^,]*,')
FROM dual
REGEXP_SUBSTR('FIR
------------------
, second field ,

REGEXP_REPLACE 函數

讓我們首先看一下傳統的 REPLACE SQL 函數,它把一個字符串用另一個字符串來替換。假設您的數據在正文中有不必要的空格,您希望用單個空格來替換它們。利用 REPLACE 函數,您需要準確地列出您要替換多少個空格。然而,多餘空格的數目在正文的各處可能不是相同的。下面的示例在 JoeSmith 之間有三個空格。REPLACE 函數的參數指定要用一個空格來替換兩個空格。在這種情況下,結果在原來的字符串的 JoeSmith 之間留下了一個額外的空格。

SELECT REPLACE('Joe Smith',' ', ' ')
AS replace
FROM dual
REPLACE
---------
Joe Smith

REGEXP_REPLACE 函數把替換功能向前推進了一步,其語法在表 9 中列出。以下查詢用單個空格替換了任意兩個或更多的空格。( ) 子表達式包含了單個空格,它可以按 {2,} 的指示重複兩次或更多次。

SELECT REGEXP_REPLACE('Joe Smith',
'( ){2,}', ' ')
AS RX_REPLACE
FROM dual
RX_REPLACE
----------
Joe Smith


ORACLE中的支持正則表達式的函數主要有下面四個:
1,REGEXP_LIKE :與LIKE的功能相似
2,REGEXP_INSTR :與INSTR的功能相似
3,REGEXP_SUBSTR :與SUBSTR的功能相似
4,REGEXP_REPLACE :與REPLACE的功能相似
它們在用法上與Oracle SQL 函數LIKE、INSTR、SUBSTR 和REPLACE 用法相同,
但是它們使用POSIX 正則表達式代替了老的百分號(%)和通配符(_)字符。
POSIX 正則表達式由標準的元字符(metacharacters)所構成:
'^' 匹配輸入字符串的開始位置,在方括號表達式中使用,此時它表示不接受該字符集合。
'$' 匹配輸入字符串的結尾位置。如果設置了 RegExp 對象的 Multiline 屬性,則 $ 也匹
配 '/n' 或 '/r'。
'.' 匹配除換行符之外的任何單字符。
'?' 匹配前面的子表達式零次或一次。
'+' 匹配前面的子表達式一次或多次。
'*' 匹配前面的子表達式零次或多次。
'|' 指明兩項之間的一個選擇。例子'^([a-z]+|[0-9]+)$'表示所有小寫字母或數字組合成的
字符串。
'( )' 標記一個子表達式的開始和結束位置。
'[]' 標記一箇中括號表達式。
'{m,n}' 一個精確地出現次數範圍,m=<出現次數<=n,'{m}'表示出現m次,'{m,}'表示至少
出現m次。
/num 匹配 num,其中 num 是一個正整數。對所獲取的匹配的引用。
字符簇:
[[:alpha:]] 任何字母。
[[:digit:]] 任何數字。
[[:alnum:]] 任何字母和數字。
[[:space:]] 任何白字符。
[[:upper:]] 任何大寫字母。
[[:lower:]] 任何小寫字母。
[[:punct:]] 任何標點符號。
[[:xdigit:]] 任何16進制的數字,相當於[0-9a-fA-F]。
各種操作符的運算優先級
/轉義符
(), (?:), (?=), [] 圓括號和方括號
*, +, ?, {n}, {n,}, {n,m} 限定符
^, $, anymetacharacter 位置和順序
|
*/
--創建表
create table fzq
(
  id varchar(4),
  value varchar(10)
);
--數據插入
insert into fzq values
('1','1234560');
insert into fzq values
('2','1234560');
insert into fzq values
('3','1b3b560');
insert into fzq values
('4','abc');
insert into fzq values
('5','abcde');
insert into fzq values
('6','ADREasx');
insert into fzq values
('7','123  45');
insert into fzq values
('8','adc  de');
insert into fzq values
('9','adc,.de');
insert into fzq values
('10','1B');
insert into fzq values
('10','abcbvbnb');
insert into fzq values
('11','11114560');
insert into fzq values
('11','11124560');
--regexp_like
--查詢value中以1開頭60結束的記錄並且長度是7位
select * from fzq where value like '1____60';
select * from fzq where regexp_like(value,'1....60');
--查詢value中以1開頭60結束的記錄並且長度是7位並且全部是數字的記錄。
--使用like就不是很好實現了。
select * from fzq where regexp_like(value,'1[0-9]{4}60');
-- 也可以這樣實現,使用字符集。
select * from fzq where regexp_like(value,'1[[:digit:]]{4}60');
-- 查詢value中不是純數字的記錄
select * from fzq where not regexp_like(value,'^[[:digit:]]+$');
-- 查詢value中不包含任何數字的記錄。
select * from fzq where regexp_like(value,'^[^[:digit:]]+$');
--查詢以12或者1b開頭的記錄.不區分大小寫。
select * from fzq where regexp_like(value,'^1[2b]','i');
--查詢以12或者1b開頭的記錄.區分大小寫。
select * from fzq where regexp_like(value,'^1[2B]');
-- 查詢數據中包含空白的記錄。
select * from fzq where regexp_like(value,'[[:space:]]');
--查詢所有包含小寫字母或者數字的記錄。
select * from fzq where regexp_like(value,'^([a-z]+|[0-9]+)$');
--查詢任何包含標點符號的記錄。
select * from fzq where regexp_like(value,'[[:punct:]]');
/*
理解它的語法就可以了。其它的函數用法類似。
*/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章