天天看點

「SQL資料分析系列」4.過濾操作

「SQL資料分析系列」4.過濾操作

來源 |  Learning SQL Generate, Manipulate, and Retrieve Data, Third Edition

作者 | Alan Beaulieu

譯者 | Liangchu

校對 | gongyouliu

編輯 | auroral-L

全文共10750字,預計閱讀時間50分鐘。

第四章  過濾操作

1.  條件評估

    1.1  使用圓括号

    1.2  使用操作符not

2.  建構條件

3.  條件類型

    3.1  相等條件

        3.1.1  不等條件

        3.1.2  使用相等條件修改資料

    3.2  範圍條件

        3.2.1  between 操作符

        3.2.2  字元串範圍

    3.3  成員條件

        3.3.1  使用子查詢

        3.3.2  使用 not in

    3.4  比對條件

        3.4.1  使用通配符

        3.4.2  使用正規表達式

4.  Null:由四個字母組成的詞

有時需要擷取表中的所有行,例如:

• 提取表中的所有資料,用于建立新的資料倉庫;

• 添加新列後,修改表中的所有行

• 從消息隊清單中檢索所有行

在這種情況下,SQL語句無需過濾任何行,是以并不需要where語句。但是,大多數情況下,你所關注的隻是表中所有行的一個子集。是以,所有SQL資料語句(insert語句除外)都包含一個可選的where子句,該子句包含一個或多個用于限制SQL語句作用的行數的過濾條件。另外,select語句包含的having子句可以包括與分組資料有關的過濾條件。本章探讨可以在select、update和delete語句的where子句中使用的各種類型的過濾條件,至于select語句的having子句的過濾條件,我将在第八章中示範。

1.  條件評估

where子句可以包含一個或多個條件,由操作符and和or分隔。如果多個條件僅由and操作符分隔,則所有條件的計算結果都必須為true,才能将該行包括在結果集中。請看下面的where子句:

WHERE first_name = 'STEVEN' AND create_date > '2006-01-01'
           

根據這兩個條件,隻有名為Steven且建立日期在2006年1月1日之後的行才會包含在結果集中。盡管本例僅使用了兩個條件,但無論where子句中有多少個條件,如果它們都由and操作符分隔,則它們的計算結果都必須為true,相應的行才能被包含到結果集中。

但是,如果where子句中的所有條件都由or操作符分隔,則隻要其中一個條件的計算結果為true,就可以将該行包括在結果集中了。考慮以下兩個條件:

WHERE first_name = 'STEVEN' OR create_date > '2006-01-01'
           

對于一行資料來說,隻要符合下面一種情況就可以被包含在結果集中:

• 名字是Steven,建立日期是2006年1月1日之後(不包括當日)。

• 名字是Steven,建立日期是2006年1月1日或之前。

• 名字不是Steven,但是建立日期是2006年1月1日之後(不包括當日)。

下表(4-1)顯示了包含兩個由or操作符分隔的條件的where子句的所有可能結果:

「SQL資料分析系列」4.過濾操作

在前面的示例中,不被包含在結果集中的唯一情況是:此人名字不是Steven,并且建立時間是2006年1月1日或之前。

1.1  使用圓括号

如果where子句包含三個或更多條件,并且同時用到了and和or操作符,則要使用圓括号使得語句的意思更容易了解(對其他資料庫伺服器以及閱讀代碼的其他開發者而言)。下面的where子句擴充了前面的示例,該過濾條件選擇名字是Steven或姓Young,并且建立日期在2006年1月1日之後的資料行到結果集中:

WHERE (first_name = 'STEVEN' OR last_name = 'YOUNG')
 AND create_date > '2006-01-01'
           

這裡有三個條件,對于資料行來說,第一個或者第二個條件值中至少有一個得是true,第三個條件的計算結果必須是true,該行才能被包括到結果集中。下表(4-2)顯示了這三個條件組合的所有可能結果:

「SQL資料分析系列」4.過濾操作

如你所見,where子句中的條件越多,伺服器需要計算的組合就越多。在這種情況下,八個組合中隻有三個組合産生的最終結果為true。

1.2  使用操作符not

希望你能了解前面包含三個條件的示例,接下來繼續考慮以下修改:

WHERE NOT (first_name = 'STEVEN' OR last_name = 'YOUNG')
 AND create_date > '2006-01-01'
           

你有看出什麼不同嗎?這個例子中,我在第一組條件之前增加了not操作符。是以現在查找的不是“名字是Steven或姓Young,并且建立日期在2006年1月1日之後”的資料行了,而是隻檢索“名字不是Steven且姓氏不是Young,并且建立日期在2006年1月1日之後”的資料行。下表(4-3)顯示了該示例可能的結果:

「SQL資料分析系列」4.過濾操作
「SQL資料分析系列」4.過濾操作

雖然對于資料庫伺服器來說,它能夠輕而易舉地處理包括not操作符的where子句,但是對于人來說,這通常比較麻煩,是以通常情況下人們較少使用它。本例中,可以重寫where子句以避免使用not操作符:

WHERE first_name <> 'STEVEN' AND last_name <> 'YOUNG'
 AND create_date > '2006-01-01'
           

盡管對于資料庫伺服器來說,上面兩個例子沒什麼差别,但是你可能會覺得後一個例子更好了解一些。

2.  建構條件

既然你已經知道了伺服器是如何評估多個條件的,那麼讓我們回過頭看看如何建立單個條件。條件由一個或多個表達式與一個或多個操作符組合而成。表達式可以是以下任一類型:

• 數字;

• 表或視圖中的列;

• 字元串,比如'Maple Street';

• 内置函數,比如concat('Learning', ' ', 'SQL');

• 子查詢;

• 表達式清單,比如('Boston', 'New York', 'Chicago');

條件中使用的操作符包括:

• 比較運算符,如=、!=、<、>、<>、like、in和between

• 算術運算符,如+、-、*和/

下一節示範如何組合這些表達式和操作符來生成各種類型的條件。

3.  條件類型

有許多方法可以用于過濾不需要的資料,比如查找特定值、值集合或要包含或排除的值範圍,還可以在處理字元串資料時使用各種模式搜尋技術來查找部分比對的行。接下來的四小節将詳細探讨每種條件類型:

3.1  相等條件

你所編寫或遇到的大部分過濾條件的格式都類似于'column = expression',例如:

title = 'RIVER OUTLAW'
fed_id = '111-11-1111'
amount = 375.25
film_id = (SELECT film_id FROM film WHERE title = 'RIVER OUTLAW')
           

像這樣的條件稱為相等條件(equality condition),因為它們将一個表達式等同于另一個表達式。前三個示例将列等同于文字(兩個字元串和一個數字),第四個示例将列等同于子查詢傳回的值。以下查詢使用兩個相等條件,一個在on子句(連接配接條件)中,另一個在where子句(過濾條件)中:

mysql> SELECT c.email
 -> FROM customer c
 -> INNER JOIN rental r
 -> ON c.customer_id = r.customer_id
 -> WHERE date(r.rental_date) = '2005-06-14';
+---------------------------------------+
| email |
+---------------------------------------+
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
+---------------------------------------+
16 rows in set (0.03 sec)
           

此查詢顯示2005年6月14日租用電影的每位客戶的電子郵件位址。

3.1.1  不等條件

另一種比較常見的條件類型是不等條件(inequality condition),它判斷兩個表達式不相等。下面是将上一個查詢where子句中的過濾條件改為不等條件的語句示例:

mysql> SELECT c.email
 -> FROM customer c
 -> INNER JOIN rental r
 -> ON c.customer_id = r.customer_id
 -> WHERE date(r.rental_date) <> '2005-06-14';
+-----------------------------------+
| email |
+-----------------------------------+
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
...
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
| [email protected] |
+-----------------------------------+
16028 rows in set (0.03 sec)
           

此查詢傳回2005年6月14日以外任何日期租用電影的客戶的所有電子郵件位址。在構造不等條件時,你可以選擇使用!=或<>操作符。

3.1.2  使用相等條件修改資料

修改資料時通常會用到相等/不相等條件。例如,假設電影租賃公司每年都會移除舊帳戶的行資料,那麼就需要從rental表中删除出租日期為2004年的行。下面是實作方式:

DELETE FROM rental
WHERE year(rental_date) = 2004;
           

此語句僅包含一個相等條件。下面的示例使用兩個不等條件删除出租日期不在2005年或2006年的所有行資料:

DELETE FROM rental
WHERE year(rental_date) <> 2005 AND year(rental_date) <> 2006;
           

注意

在編寫delete和update語句的示例時,我盡可能保證不會修改任何一行資料。這樣在執行這些語句後資料能保持不變,保證select語句的輸出始終與本書中顯示的一樣。

由于MySQL會話在預設情況下處于自動送出模式(參見第十二章),是以如果有某個語句修改了示例資料,則無法復原(撤消)對該資料所做的任何更改。當然你可以随意操縱你的示例資料,甚至可以完全删除這些資料然後重新運作腳本以填充表資料,但我還是會盡量保持資料不變。

3.2  範圍條件

除了檢查一個表達式是否等于(或不等于)另一個表達式外,還可以建構條件來檢查表達式是否在某個範圍内。這種情況通常用于數值型資料或時間資料。考慮以下查詢:

mysql> SELECT customer_id, rental_date 
 -> FROM rental 
 -> WHERE rental_date < '2005-05-25'; 
+-------------+---------------------+ 
| customer_id | rental_date | 
+-------------+---------------------+ 
| 130 | 2005-05-24 22:53:30 | 
| 459 | 2005-05-24 22:54:33 | 
| 408 | 2005-05-24 23:03:39 | 
| 333 | 2005-05-24 23:04:41 | 
| 222 | 2005-05-24 23:05:21 | 
| 549 | 2005-05-24 23:08:07 | 
| 269 | 2005-05-24 23:11:53 | 
| 239 | 2005-05-24 23:31:46 | 
+-------------+---------------------+ 
8 rows in set (0.00 sec)
           

此查詢查找2005年5月25日之前所有的電影租賃資料。除了指定租賃日期的上限外,或許還需要指定日期的下限:

mysql> SELECT customer_id, rental_date 
 -> FROM rental 
 -> WHERE rental_date <= '2005-06-16' 
 -> AND rental_date >= '2005-06-14'; 
+-------------+---------------------+ 
| customer_id | rental_date | 
+-------------+---------------------+ 
| 416 | 2005-06-14 22:53:33 | 
| 516 | 2005-06-14 22:55:13 | 
| 239 | 2005-06-14 23:00:34 | 
| 285 | 2005-06-14 23:07:08 | 
| 310 | 2005-06-14 23:09:38 | 
| 592 | 2005-06-14 23:12:46 | 
... 
| 148 | 2005-06-15 23:20:26 | 
| 237 | 2005-06-15 23:36:37 | 
| 155 | 2005-06-15 23:55:27 | 
| 341 | 2005-06-15 23:57:20 | 
| 149 | 2005-06-15 23:58:53 | 
+-------------+---------------------+ 
364 rows in set (0.00 sec)
           

該查詢檢索2005年6月14日或15日所有的電影租賃資料。

3.2.1  between操作符

當範圍既有上限又有下限時,可以選擇使用between操作符建構一個查詢條件,而不是組合使用兩個單獨的限制條件:

mysql> SELECT customer_id, rental_date
 -> FROM rental
 -> WHERE rental_date BETWEEN '2005-06-14' AND '2005-06-16';
+-------------+---------------------+
| customer_id | rental_date |
+-------------+---------------------+
| 416 | 2005-06-14 22:53:33 |
| 516 | 2005-06-14 22:55:13 |
| 239 | 2005-06-14 23:00:34 |
| 285 | 2005-06-14 23:07:08 |
| 310 | 2005-06-14 23:09:38 |
| 592 | 2005-06-14 23:12:46 |
...
| 148 | 2005-06-15 23:20:26 |
| 237 | 2005-06-15 23:36:37 |
| 155 | 2005-06-15 23:55:27 |
| 341 | 2005-06-15 23:57:20 |
| 149 | 2005-06-15 23:58:53 |
+-------------+---------------------+
364 rows in set (0.00 sec)
           

在使用between操作符時,需要注意:應始終先指定範圍的下限(在between之後),然後指定範圍的上限(在end之後)。如果錯誤地指定了它們出現的次序,會發生以下情況:

mysql> SELECT customer_id, rental_date
 -> FROM rental
 -> WHERE rental_date BETWEEN '2005-06-16' AND '2005-06-14';
Empty set (0.00 sec)
           

如你所見,結果沒有資料傳回,這是因為伺服器實際上使用<=和>=操作符從你給出的單個條件生成兩個條件,如下:

SELECT customer_id, rental_date
 -> FROM rental
 -> WHERE rental_date >= '2005-06-16' 
 -> AND rental_date <= '2005-06-14'
Empty set (0.00 sec)
           

由于不可能出現一個既大于2005年6月16日又小于2005年6月14日的日期,是以查詢傳回一個空集。這裡還應該注意到使用between操作符時的第二個注意事項,也就是:範圍的上下限是閉合的,也即上限和下限的值本身都被包含在範圍内。在本例中,我希望輸出傳回6月14日或15日租用的所有影片,是以我指定2005-06-14為範圍的下限,2005-06-16為上限。由于我沒有指定日期的時間部分,是以時間預設為半夜,故而有效範圍為2005-06-14 00:00:00到2005-06-16 00:00:00,這将包括6月14日或15日的所有租借情況。

除了日期之外,還可以建構條件來指定數字的範圍,這種方式相當容易掌握,如下所示:

mysql> SELECT customer_id, payment_date, amount
 -> FROM payment
 -> WHERE amount BETWEEN 10.0 AND 11.99;
+-------------+---------------------+--------+
| customer_id | payment_date | amount |
+-------------+---------------------+--------+
| 2 | 2005-07-30 13:47:43 | 10.99 |
| 3 | 2005-07-27 20:23:12 | 10.99 |
| 12 | 2005-08-01 06:50:26 | 10.99 |
| 13 | 2005-07-29 22:37:41 | 11.99 |
| 21 | 2005-06-21 01:04:35 | 10.99 |
| 29 | 2005-07-09 21:55:19 | 10.99 |
...
| 571 | 2005-06-20 08:15:27 | 10.99 |
| 572 | 2005-06-17 04:05:12 | 10.99 |
| 573 | 2005-07-31 12:14:19 | 10.99 |
| 591 | 2005-07-07 20:45:51 | 11.99 |
| 592 | 2005-07-06 22:58:31 | 11.99 |
| 595 | 2005-07-31 11:51:46 | 10.99 |
+-------------+---------------------+--------+
114 rows in set (0.01 sec)
           

10美元到11.99美元之間的所有付款都将被退回。同樣,請先指定範圍的下限。

3.2.2  字元串範圍

雖然日期和數字的範圍很容易了解,但同樣可以建構搜尋字元串範圍的條件,這一點可能不是那麼好了解。比如:你正在搜尋姓氏在某個範圍内的客戶。下面的查詢傳回姓氏介于FA和FR之間的客戶:

mysql> SELECT last_name, first_name 
 -> FROM customer 
 -> WHERE last_name BETWEEN 'FA' AND 'FR'; 
+------------+------------+ 
| last_name | first_name | 
+------------+------------+ 
| FARNSWORTH | JOHN | 
| FENNELL | ALEXANDER | 
| FERGUSON | BERTHA | 
| FERNANDEZ | MELINDA | 
| FIELDS | VICKI | 
| FISHER | CINDY | 
| FLEMING | MYRTLE | 
| FLETCHER | MAE | 
| FLORES | JULIA | 
| FORD | CRYSTAL | 
| FORMAN | MICHEAL | 
| FORSYTHE | ENRIQUE | 
| FORTIER | RAUL | 
| FORTNER | HOWARD | 
| FOSTER | PHYLLIS | 
| FOUST | JACK | 
| FOWLER | JO | 
| FOX | HOLLY | 
+------------+------------+ 
18 rows in set (0.00 sec)
           

雖然有五個客戶的姓氏以FR開頭,但結果中不包括他們,因為像FRANKLIN這樣的名字超出了範圍。但是,我們可以通過将右側範圍擴大到FRB來将五個客戶中的四個包括在内:

mysql> SELECT last_name, first_name
 -> FROM customer
 -> WHERE last_name BETWEEN 'FA' AND 'FRB';
+------------+------------+
| last_name | first_name |
+------------+------------+
| FARNSWORTH | JOHN |
| FENNELL | ALEXANDER |
| FERGUSON | BERTHA |
| FERNANDEZ | MELINDA |
| FIELDS | VICKI |
| FISHER | CINDY |
| FLEMING | MYRTLE |
| FLETCHER | MAE |
| FLORES | JULIA |
| FORD | CRYSTAL |
| FORMAN | MICHEAL |
| FORSYTHE | ENRIQUE |
| FORTIER | RAUL |
| FORTNER | HOWARD |
| FOSTER | PHYLLIS |
| FOUST | JACK |
| FOWLER | JO |
| FOX | HOLLY |
| FRALEY | JUAN |
| FRANCISCO | JOEL |
| FRANKLIN | BETH |
| FRAZIER | GLENDA |
+------------+------------+
22 rows in set (0.00 sec)
           

使用字元串範圍的時候,需要知道字元集中各個字元的順序(某個字元集中各字元的次序叫做排序順序(collation))。

3.3  成員條件

在某些情況下,不需要将表達式限制為特定值或某個範圍值,而是限制為一組有限值集合。例如,要定位所有分級為“G”或“PG”的影片:

mysql> SELECT title, rating
 -> FROM film
 -> WHERE rating = 'G' OR rating = 'PG';
+---------------------------+--------+
| title | rating |
+---------------------------+--------+
| ACADEMY DINOSAUR | PG |
| ACE GOLDFINGER | G |
| AFFAIR PREJUDICE | G |
| AFRICAN EGG | G |
| AGENT TRUMAN | PG |
| ALAMO VIDEOTAPE | G |
| ALASKA PHANTOM | PG |
| ALI FOREVER | PG |
| AMADEUS HOLY | PG |
...
| WEDDING APOLLO | PG |
| WEREWOLF LOLA | G |
| WEST LION | G |
| WIZARD COLDBLOODED | PG |
| WON DARES | PG |
| WONDERLAND CHRISTMAS | PG |
| WORDS HUNTER | PG |
| WORST BANGER | PG |
| YOUNG LANGUAGE | G |
+---------------------------+--------+
           

雖然上例中where子句(兩個條件由or組合在一起)的生成并不繁瑣,但是想象一下,如果表達式集包含10或20個成員,那麼處理将會很麻煩。對于這些情況,可以改用in操作符:

SELECT title, rating
FROM film
WHERE rating IN ('G','PG');
           

使用in操作符,無論集合中有多少個表達式,都可以由一個條件語句完成。

3.3.1  使用子查詢

除了編寫自己的表達式集,例如('G','PG'),還可以使用子查詢來動态生成一個表達式集。例如,假設标題中包含字元串“PET”的所有影片都适合一家人觀看,那麼可以對film表進行子查詢來檢索與這些影片相關聯的所有分級,然後檢索具有指定分級的所有影片:

mysql> SELECT title, rating
 -> FROM film
 -> WHERE rating IN (SELECT rating FROM film WHERE title LIKE '%PET%');
+---------------------------+--------+
| title | rating |
+---------------------------+--------+
| ACADEMY DINOSAUR | PG |
| ACE GOLDFINGER | G |
| AFFAIR PREJUDICE | G |
| AFRICAN EGG | G |
| AGENT TRUMAN | PG |
| ALAMO VIDEOTAPE | G |
| ALASKA PHANTOM | PG |
| ALI FOREVER | PG |
| AMADEUS HOLY | PG |
...
| WEDDING APOLLO | PG |
| WEREWOLF LOLA | G |
| WEST LION | G |
| WIZARD COLDBLOODED | PG |
| WON DARES | PG |
| WONDERLAND CHRISTMAS | PG |
| WORDS HUNTER | PG |
| WORST BANGER | PG |
| YOUNG LANGUAGE | G |
+---------------------------+--------+
372 rows in set (0.00 sec)
           

子查詢傳回集合'G'和'PG',主查詢檢查是否可以在子查詢傳回的集合中找到rating列的值。

3.3.2  使用not in

有時你想知道某特定表達式是否存在于一組表達式中,有時你也想知道該表達式是否不存在于該組表達式中。對于這些情況,可以使用not in操作符:

SELECT title, rating 
FROM film 
WHERE rating NOT IN ('PG-13','R', 'NC-17');
           

此查詢查找所有評級不是'PG-13' ,'R',或'NC-17'的電影記錄,它将傳回與上個查詢一樣的327行結果集。

3.4  比對條件

到目前為止,本章已經向你介紹了識别單個字元串、字元串範圍或字元串集合的條件,最後要介紹的條件類型是處理部分字元串比對。例如,你可能希望查找姓氏以Q開頭的所有客戶,則可以使用内置函數去取last_name列的第一個字母,如下所示:

mysql> SELECT last_name, first_name
 -> FROM customer
 -> WHERE left(last_name, 1) = 'Q';
+-------------+------------+
| last_name | first_name |
+-------------+------------+
| QUALLS | STEPHEN |
| QUINTANILLA | ROGER |
| QUIGLEY | TROY |
+-------------+------------+
3 rows in set (0.00 sec)
           

雖然内置函數left()能完成這項工作,但是它的靈活度不高,是以你可以使用通配符來建構搜尋表達式,如下一節所示。

3.4.1  使用通配符

搜尋部分字元串比對時,你可能會對下面的内容感興趣:

• 以特定字元開頭/結尾的字元串;

• 以子字元串開頭/結尾的字元串;

• 在字元串的任意位置包含特定字元的字元串;

• 在字元串的任意位置包含子字元串的字元串;

• 具有特定格式且不考慮單個字元的字元串。

通過使用下表(4-4)所示的通配符,你可以建構搜尋表達式來識别這些比對項和許多其他部分字元串比對項:

「SQL資料分析系列」4.過濾操作

下劃線字元可以代替單個字元,而百分号可以代替多個字元。在使用搜尋表達式建構條件的時候,可以使用like操作符,如下所示:

mysql> SELECT last_name, first_name
 -> FROM customer
 -> WHERE last_name LIKE '_A_T%S';
+-----------+------------+
| last_name | first_name |
+-----------+------------+
| MATTHEWS | ERICA |
| WALTERS | CASSANDRA |
| WATTS | SHELLY |
+-----------+------------+
3 rows in set (0.00 sec)
           

上一個示例中的搜尋表達式指定了第二個位置包含A,第四個位置包含T,後跟任意數量的字元,并且以S結尾的字元串。下表(4-5)展示了更多的搜尋表達式及其解釋:

「SQL資料分析系列」4.過濾操作
「SQL資料分析系列」4.過濾操作

使用通配符,可以很友善地建構簡單的搜尋表達式,但是,如果需要處理更複雜的情況,則可以使用多個搜尋表達式,如下所示:

mysql> SELECT last_name, first_name
 -> FROM customer
 -> WHERE last_name LIKE 'Q%' OR last_name LIKE 'Y%';
+-------------+------------+
| last_name | first_name |
+-------------+------------+
| QUALLS | STEPHEN |
| QUIGLEY | TROY |
| QUINTANILLA | ROGER |
| YANEZ | LUIS |
| YEE | MARVIN |
| YOUNG | CYNTHIA |
+-------------+------------+
6 rows in set (0.00 sec)
           

該查詢查找所有姓氏以Q或者Y開頭的顧客。

3.4.2  使用正規表達式

如果你覺得帶通配符的字元串仍然不夠靈活,那麼可以使用正規表達式來建構搜尋表達式。正規表達式本質上也是一種搜尋表達式。如果你不熟悉SQL,但使用過Perl等程式設計語言,那麼你可能已經非常熟悉正規表達式了。如果你從未使用過正規表達式,那麼你可能需要查閱Jeffrey E. F. Friedl的Mastering Regular Expressions (O’Reilly),因為這本書涉及的内容太多了,是以本書不予介紹。

在MySQL中使用正規表達式實作前面的查詢(查找姓氏以Q或Y開頭的所有客戶),如下所示:

mysql> SELECT last_name, first_name
 -> FROM customer
 -> WHERE last_name REGEXP '^[QY]';
+-------------+------------+
| last_name | first_name |
+-------------+------------+
| YOUNG | CYNTHIA |
| QUALLS | STEPHEN |
| QUINTANILLA | ROGER |
| YANEZ | LUIS |
| YEE | MARVIN |
| QUIGLEY | TROY |
+-------------+------------+
6 rows in set (0.16 sec)
           

regexp操作符接受一個正規表達式(本例中為'^[QY]'),并将其應用于條件左側的表達式(last_name列)。查詢現在隻包含一個使用正規表達式的條件,而不是兩個使用通配符的條件。

Oracle Database和Microsoft SQL Server也都支援正規表達式。對于Oracle Database,可以使用regexp_like函數而不是上一個示例中所示的regexp操作符,而SQL Server允許在like操作符中使用正規表達式。

4.  Null:由四個字母組成的詞

我盡可能将null這個令人感到模糊和恐懼的内容拖到後面講,是以現在是時候面對這個問題了。null表示沒有值,比如,在員工離職前,employee表中的end_date列應該是空值也就是null,在這種情況下,給end_date配置設定值是沒有意義的。當然null的使用方式比較靈活,可以有不同的适用場景:

• 無合适值

 例如,在ATM機上發生的交易并不需要employee表的employee ID。

• 值未知

 例如,在建立客戶行時還不知道他的federal ID。

• 值未定義

 例如,為尚未添加到資料庫的産品建立帳戶。

注意

一些理論家認為,對于不同的情況應該提供不同的表達式來表示,但是大多實踐專家認為這樣做會帶來更多困擾。

使用null時,應記住:

• 表達式可以為null,但不能等于null;

• 兩個null永遠不可判等。

要測試表達式是否為null,需要使用is null操作符,如下所示:

mysql> SELECT rental_id, customer_id 
 -> FROM rental 
 -> WHERE return_date IS NULL; 
+-----------+-------------+ 
| rental_id | customer_id | 
+-----------+-------------+ 
| 11496 | 155 | 
| 11541 | 335 | 
| 11563 | 83 | 
| 11577 | 219 | 
| 11593 | 99 | 
... 
| 15867 | 505 | 
| 15875 | 41 | 
| 15894 | 168 | 
| 15966 | 374 | 
+-----------+-------------+ 
183 rows in set (0.01 sec)
           

此查詢查找所有沒有歸還的電影的租借情況。下面是使用=null而不是is null實作相同查詢的語句:

mysql> SELECT rental_id, customer_id 
 -> FROM rental 
 -> WHERE return_date = NULL; 
Empty set (0.01 sec)
           

如你所見,該查詢解析并執行後沒有傳回任何行,沒有經驗的SQL程式員常常會犯這種錯誤,并且資料庫伺服器也不會産生該錯誤相關的警告資訊,是以在建構有關null的查詢時應該謹慎。

如果要檢視是否已将值指定給某列,可以使用is not null操作符,如下所示:

mysql> SELECT rental_id, customer_id, return_date 
 -> FROM rental 
 -> WHERE return_date IS NOT NULL; 
+-----------+-------------+---------------------+ 
| rental_id | customer_id | return_date | 
+-----------+-------------+---------------------+ 
| 1 | 130 | 2005-05-26 22:04:30 | 
| 2 | 459 | 2005-05-28 19:40:33 | 
| 3 | 408 | 2005-06-01 22:12:39 | 
| 4 | 333 | 2005-06-03 01:43:41 | 
| 5 | 222 | 2005-06-02 04:33:21 | 
| 6 | 549 | 2005-05-27 01:32:07 | 
| 7 | 269 | 2005-05-29 20:34:53 | 
... 
| 16043 | 526 | 2005-08-31 03:09:03 | 
| 16044 | 468 | 2005-08-25 04:08:39 | 
| 16045 | 14 | 2005-08-25 23:54:26 | 
| 16046 | 74 | 2005-08-27 18:02:47 | 
| 16047 | 114 | 2005-08-25 02:48:48 | 
| 16048 | 103 | 2005-08-31 21:33:07 | 
| 16049 | 393 | 2005-08-30 01:01:12 | 
+-----------+-------------+---------------------+ 
15861 rows in set (0.02 sec)
           

這個查詢傳回歸還的所有租金,包含了表中的大多數行(16044行中有15861行)。

接着介紹有關null的一個易犯錯誤。假設你被要求查找2005年5月至8月期間未歸還的所有租金。你的第一反應可能是:

mysql> SELECT rental_id, customer_id, return_date 
 -> FROM rental 
 -> WHERE return_date NOT BETWEEN '2005-05-01' AND '2005-09-01'; 
+-----------+-------------+---------------------+ 
| rental_id | customer_id | return_date | 
+-----------+-------------+---------------------+ 
| 15365 | 327 | 2005-09-01 03:14:17 | 
| 15388 | 50 | 2005-09-01 03:50:23 | 
| 15392 | 410 | 2005-09-01 01:14:15 | 
| 15401 | 103 | 2005-09-01 03:44:10 | 
| 15415 | 204 | 2005-09-01 02:05:56 | 
... 
| 15977 | 550 | 2005-09-01 22:12:10 | 
| 15982 | 370 | 2005-09-01 21:51:31 | 
| 16005 | 466 | 2005-09-02 02:35:22 | 
| 16020 | 311 | 2005-09-01 18:17:33 | 
| 16033 | 226 | 2005-09-01 02:36:15 | 
| 16037 | 45 | 2005-09-01 02:48:04 | 
| 16040 | 195 | 2005-09-02 02:19:33 | 
+-----------+-------------+---------------------+ 
62 rows in set (0.01 sec)
           

雖然這62筆租金确實是在5月到8月之外的日期退回的,但如果仔細觀察資料,你會發現結果集中所有行的退回日期非空。那183筆從未歸還的租金(也就是return_date為null)又怎麼處理?有人可能會說,這183行在5月和8月之間也沒有退回,是以它們也應該包含在結果集中。故而要正确回答問題,你需要考慮某些行在return_date列中可能包含null的情況:

mysql> SELECT rental_id, customer_id, return_date 
 -> FROM rental 
 -> WHERE return_date IS NULL 
 -> OR return_date NOT BETWEEN '2005-05-01' AND '2005-09-01'; 
+-----------+-------------+---------------------+ 
| rental_id | customer_id | return_date | 
+-----------+-------------+---------------------+ 
| 11496 | 155 | NULL | 
| 11541 | 335 | NULL | 
| 11563 | 83 | NULL | 
| 11577 | 219 | NULL | 
| 11593 | 99 | NULL | 
... 
| 15939 | 382 | 2005-09-01 17:25:21 | 
| 15942 | 210 | 2005-09-01 18:39:40 | 
| 15966 | 374 | NULL | 
| 15971 | 187 | 2005-09-02 01:28:33 | 
| 15973 | 343 | 2005-09-01 20:08:41 | 
| 15977 | 550 | 2005-09-01 22:12:10 | 
| 15982 | 370 | 2005-09-01 21:51:31 | 
| 16005 | 466 | 2005-09-02 02:35:22 | 
| 16020 | 311 | 2005-09-01 18:17:33 | 
| 16033 | 226 | 2005-09-01 02:36:15 | 
| 16037 | 45 | 2005-09-01 02:48:04 | 
| 16040 | 195 | 2005-09-02 02:19:33 | 
+-----------+-------------+---------------------+ 
245 rows in set (0.01 sec)
           

結果集現在包括了5月至8月之外退回的62份租金,以及183份尚未退回的租金,總共245行資料。在處理你不熟悉的資料庫時,最好知道表中哪些列允許null值,這樣你就可以對過濾條件采取适當的措施,以防止需要的資料被漏掉。

「SQL資料分析系列」4.過濾操作
「SQL資料分析系列」4.過濾操作