《MySQL 入門教程》第 09 篇 字符串模式匹配

上一篇我們介紹瞭如何通過 WHERE 查詢條件過濾數據,包括比較運算符、邏輯運算符以及空值判斷等。本篇我們來討論一下字符串的模糊查找。

9.1 LIKE 運算符

MySQL 中的LIKE運算符可以用於判斷字符串是否包含某個模式。使用LIKE運算符的語法如下:

expr LIKE pat

如果表達式 expr 能夠匹配模式 pat,結果返回 True(1);否則,返回 False(0)。如果 expr 或者 pat 爲 NULL, 返回 NULL。

根據 SQL 標準,MySQL 支持兩個通配符:

  • 百分號(%)匹配零個或多個任意字符。
  • 下劃線(_)匹配一個任意字符。

例如,以下語句用於查詢“關”姓員工:

select emp_name
from employee
where emp_name like '關%';
  emp_name|
----------|
關興      |
關平      |
關羽      |

其中,“關%”表示以“關”字開始的字符串。另外,“%xyz%”表示包含 xyz 的字符串;“%xyz”表示以 xyz 結束的字符串。

以下語句演示了下劃線的作用:

select emp_name
from employee
where emp_name like '孫__';
  emp_name|
----------|
孫丫鬟     |
孫尚香     |

其中,“孫__”表示以“孫”字開始並且姓名爲三個字的員工;因此查詢結果中沒有包含“孫乾”。

9.1.1 轉義字符

由於百分號和下劃線是通配符,具有特殊的意義。當我們想要判斷字符串中是否包含這兩個字符時,例如“50%”,就需要使用一個轉義字符將模式中的通配符解釋爲普通字符。轉義字符使用ESCAPE進行指定:

expr LIKE pat ESCAPE 'escape_char'

默認情況下,MySQL 使用反斜線(\)作爲轉義字符。例如:

select '完成進度:50% 已完成。' like '%50%%' as like1, '日期 20150101' like '%50%%' as like2;
like1|like2|
-----|-----|
    1|    1|

select '完成進度:50% 已完成。' like '%50\%%' as like1, '日期 20150101' like '%50\%%' as like2;
like1|like2|
-----|-----|
    1|    0|

第一個查詢沒有使用轉義字符,直接使用“50%” 進行匹配,結果“日期 20150101”也滿足條件;第二個查詢使用轉義字符,“50%”只匹配百分之五十(50%)。

我們也可以指定其他的轉義字符,例如:

select '2020_06_13' like '%06#_13%' escape '#' as like3;
like3|
-----|
    1|

以上語句使用“#”作爲轉義字符。此時如果字符串中存在“#”,需要連寫兩個“#”表示匹配井號自身。

另外,需要注意 MySQL 中的LIKE運算符不區分大小寫。例如:

select 'Tony' like 'tony';
'Tony' like 'tony'|
------------------|
                 1|

如果想要實現區分大小寫的匹配,可以使用下文中的REGEXP_LIKE函數。

上一篇中介紹的NOT運算符可以將表達式的結果進行取反,因此NOT LIKE運算符可以進行反向模式匹配。例如:

select emp_name, email
from employee
where email not like '%a%';
  emp_name|email             |
----------|------------------|
劉備      |liubei@shuguo.com |
糜竺      |mizhu@shuguo.com  |
鄧芝      |dengzhi@shuguo.com|

該語句返回了 email 中不包含字母 a 的員工。

9.2 正則表達式匹配

LIKE 運算符可以實現簡單的模式匹配。但是當我們需要匹配更復雜的模式時,例如判斷用戶輸入的電子郵箱是否合法,則無法通過 LIKE 運算符實現。爲此, MySQL 提供了更加強大的正則表達式(Regular Expression)函數和運算符。

正則表達式是一個包含字母、數字和特殊符號的模式,可以用於檢索或者替換符合某個模式(規則)的文本字符串。正則表達式可以用於查找電子郵箱、IP 地址、身份證等具有特定規則的數據,也可以用於驗證用戶名是否符合指定規則(例如只包含字符、數字、下劃線並且字符數量爲某個範圍)。

📝關於正則表達式的具體內容可以參考 GitHub上的 正則表達式教程

MySQL 提供以下三種的正則表達式匹配函數和運算符:

REGEXP_LIKE(expr, pat[, match_type])
expr REGEXP pat
expr RLIKE pat

如果字符串 expr 匹配模式 pat 指定的正則表達式,返回 Ture(1),否則返回 False(0)。如果 expr 或者 pat 爲 NULL,返回 NULL。可選參數 match_type 可以用於指定匹配方式,可以是以下選項之一或者全部:

  • c:區分大小寫;
  • i:不區分大小寫,默認方式;
  • m:多行匹配,可以識別字符串內部的行終止符。默認情況下只在字符串的開始或結束匹配行終止符;
  • n:點號( . )匹配行終止符。默認情況下,點號遇到一行的結束時會終止匹配;
  • u:只匹配 Unix 行終止符,此時只有換行符被 .、^ 和 $ 看作一行的結束。

REGEXP 和 RLIKE 運算符實際上是 REGEXP_LIKE 函數的同義詞,但是不支持 match_type 匹配選項,而是使用默認選項。

我們看一個區分大小寫的匹配示例:

select regexp_like('Tony', 'tony') as regexp1, regexp_like('Tony', 'tony', 'c') as regexp2;
regexp1|regexp2|
-------|-------|
      1|      0|

默認情況下不區分大小寫,所以返回了 1;c 選項表示區分大小寫,因此返回了 0。

正則表達式的強大之處在於它提供了許多元字符(metacharacter),可以用於構造複雜的模式。下表列出了 MySQL 支持的元字符:

元字符 作用 示例
^ 匹配字符串的開始 SELECT REGEXP_LIKE(‘fo\nfo’, ‘^fo$’); -- 0
SELECT REGEXP_LIKE(‘fofo’, ‘^fo’); -- 1
$ 匹配字符串的結束 SELECT REGEXP_LIKE(‘fo\no’, ‘^fo\no$’); -- 1
SELECT REGEXP_LIKE(‘fo\no’, ‘^fo$’); -- 0
. 匹配任意單個字符 SELECT REGEXP_LIKE(‘fofo’, ‘^f.*$’); -- 1
SELECT REGEXP_LIKE(‘fo\r\nfo’, ‘^f.*$’); -- 0
SELECT REGEXP_LIKE(‘fo\r\nfo’, ‘^f.*$’, ‘m’); -- 1
SELECT REGEXP_LIKE(‘fo\r\nfo’, ‘(?m)^f.*$’); -- 1
* 匹配前面的字符零次或者多次 SELECT REGEXP_LIKE(‘Baaan’, ‘^Ba*n’); -- 1
SELECT REGEXP_LIKE(‘Ban’, ‘^Ba*n’); -- 1
SELECT REGEXP_LIKE(‘Bn’, ‘^Ba*n’); -- 1
+ 匹配前面的字符一次或者多次 SELECT REGEXP_LIKE(‘Ban’, ‘^Ba+n’); -- 1
SELECT REGEXP_LIKE(‘Bn’, ‘^Ba+n’); -- 0
? 匹配前面的字符零次或者一次 SELECT REGEXP_LIKE(‘Bn’, ‘^Ba?n’); -- 1
SELECT REGEXP_LIKE(‘Ban’, ‘^Ba?n’); -- 1
SELECT REGEXP_LIKE(‘Baan’, ‘^Ba?n’); -- 0
| 匹配左邊或者右邊的模式 SELECT REGEXP_LIKE(‘pi’, ‘pi|apa’); -- 1
SELECT REGEXP_LIKE(‘apa’, ‘pi|apa’); -- 1
SELECT REGEXP_LIKE(‘axe’, ‘pi|apa’); -- 0
{n} 匹配前面的字符 n 次 SELECT REGEXP_LIKE(‘abcde’, ‘a[bcd]{3}e’); – 1
SELECT REGEXP_LIKE(‘abcde’, ‘a[bcd]{2}e’); – 0
{m,n} 匹配前面的字符 m 次到 n 次 SELECT REGEXP_LIKE(‘abcde’, ‘a[bcd]{1,10}e’); -- 1
[…] 匹配方括號內的任意單個字符,支持使用連字符號(-)指定一個範圍 SELECT REGEXP_LIKE(‘aXbc’, ‘[a-dXYZ]’); -- 1
[^…] 匹配不在方括號內的任意單個字符,支持使用連字符號(-)指定一個範圍 SELECT REGEXP_LIKE(‘gheis’, ‘[^a-dXYZ]+’); -- 1
[:character_class:] 匹配一個字符類,例如 [:alnum:] 可以匹配所有的字母和數字 SELECT REGEXP_LIKE(‘justalnums’, ‘[[:alnum:]]+’); -- 1
SELECT REGEXP_LIKE(’!!’, ‘[[:alnum:]]+’); -- 0

對於 Web 開發中常見郵箱地址合法性驗證,可以採用以下簡單的規則:

  • 以字母或者數字開頭;
  • 後面是一個或者多個字母、數組或特殊字符( . _ - );
  • 然後是一個 @ 字符;
  • 之後包含一個或者多個字母、數組或特殊字符( . - );
  • 最後是域名,即 . 以及 2 到 4 個字母。

使用正則表達式可以表示爲:

^[a-zA-Z0-9]+[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$

其中,^ 匹配字符串的開頭;[a-zA-Z0-9] 匹配大小寫字母或數字;+ 表示匹配前面的內容一次或多次;. 匹配任何一個字符,\. 匹配點號自身;{2,4} 匹配前面的內容 2 次到 4次;$ 匹配字符串的結束。

我們創建一個測試表:

CREATE TABLE t_regexp (
  email VARCHAR(50)
);

INSERT INTO t_regexp VALUES ('[email protected]');
INSERT INTO t_regexp VALUES ('test@shuguo');
INSERT INTO t_regexp VALUES ('[email protected]');
INSERT INTO t_regexp VALUES ('[email protected]');
INSERT INTO t_regexp VALUES ('me.me@ shuguo.com');
INSERT INTO t_regexp VALUES ('[email protected]');

使用以下語句查找合法的郵箱地址:

SELECT email
  FROM t_regexp
 WHERE REGEXP_LIKE(email, '^[a-zA-Z0-9]+[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$');

email                     |
--------------------------|
TEST@shuguo.com           |
123.test@shuguo-sanguo.org|

查詢返回了兩個合法的郵箱地址。注意其中的轉義字符需要使用兩個反斜線(\),因爲 MySQL 解析器會解析一個反斜線,正則表達式會解析另一個。

📝表達式expr NOT REGEXP patexpr NOT RLIKE pat可以用於執行反向模式匹配。

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