數據庫之簡單SQL(二)

本篇文章繼數據庫之簡單SQL(一),繼續介紹SQL的基本結構和概念。

集合運算

SQL作用在關係上的union、intersect和except運算對應於數學集合論中的∪、∩和-運算。但MySQL只支持交運算,因此此處我們只介紹交運算。

想找出province_id爲110000 並且first_letter爲bj和province_id爲120000並且first_letter爲tj的所有城市,SQL如下:

(SELECT city_name
 FROM city
 WHERE province_id = 110000 AND first_letter = 'bj')
 union
 (SELECT city_name
 FROM city
 WHERE province_id = 120000 AND first_letter = 'tj')

與select子句不同,union運算自動去除重複。如果我們想保留所有重複,可以使用union all代替union。

空值

空值給關係運算帶來了特殊的問題,包括算術運算、比較運算和集合運算。

如果算術表達式的任一輸入爲空,則該算術表達式(例如+、-、*、/)結果爲空。

比較運算“1<null”,因爲不知道空值代表的是什麼,所以此比較爲真或爲假都可能是錯誤的。因此SQL將涉及空值的任何比較運算的結果視爲unknown,這創建了除了true和false之外的第三個邏輯值。

由於在where子句的謂詞中可以對比較結果使用諸如and、or和not的布爾運算,所以這些布爾運算的定義也被擴展到可以處理unknown值。

  1. and:true AND unknown的結果是unknown,false AND unknown的結果是false,unknown AND unknown的結果是unknown;
  2. or:true OR unknown的結果是true,false OR unknown的結果是unknown,unknown OR unknown的結果是unknown;
  3. not:NOT unknown的結果是unknown。

SQL在謂詞中可以使用特殊的關鍵詞null測試空值。例如我們想找出city關係中province_id爲空值的所有城市,SQL如下:

SELECT * FROM city WHERE province_id IS NULL;

類似的,謂詞is not null可以用來測試非空值。

SQL實現還允許使用子句is unknown和is not unknown來測試一個表達式的結果是否爲unknown。

當一個查詢使用select distinct子句時,重複元組將被去除。當比較兩個元組對應的屬性值時,如果這兩個值都是非空並且值相等,或者都是空,那麼它們是相同的。所以諸如{('A',null),('A',null)}這兩個元組被認爲是相同的,即使在某些屬性上存在空值,使用distinct子句會保留這樣的相同元組的一份拷貝。需要注意的是,上述對待空值的方式與謂詞中對待空值的方式是不同的,在謂詞中'null=null'會返回unknown(MySQL返回空),而不是true。

聚集函數

聚集函數是以值的一個集合爲輸入、返回單個值的函數。SQL提供了五個固有聚集函數:平均值(avg)、最小值(min)、最大值(max)、總和(sum)、計數(count)。sum和avg的輸入必須是數字集,但其他運算符還可以作用在非數字數據類型的集合上,例如字符串。

基本聚集

想找出pronvince_id爲130000的所有城市id的平均值,該查詢SQL如下:

SELECT avg(city_id)
FROM city
WHERE province_id = 130000;

我們經常使用聚集函數count計算一個關係中元組的個數,該函數的寫法是count(*)。例如我們想找出city關係中有多少個城市,可寫成:

SELECT COUNT(*) FROM city;

有些情況下在計算聚集函數前需先刪掉重複元組,這是我們可以在聚集表達式中使用關鍵詞distinct。例如我們想找出city關係中所有的城市屬於的省份的個數,該查詢可以書寫如下:

SELECT COUNT(DISTINCT province_id) FROM city;

SQL不允許在使用count(*)時使用distinct。

分組聚集

有時候我們不僅希望將聚集函數作用在單個元組集上,也希望將其作用在一組元組集上,在SQL中可以使用group by子句實現此功能。group by子句中給出的一個或多個屬性是用來構造分組的。在group by子句中得所有屬性上取值相同的元組將被分在一個組中。

例如找出每個省份的城市的平均city_id,該查詢書寫如下:

SELECT AVG(city_id)
FROM city
GROUP BY province_id;

當SQL查詢使用分組時,一個很重要的事情就是需要保證出現在select語句中但沒有被聚集的屬性只能是出現在group by子句中的那些屬性。換句話說,任何沒有出現在group by子句中的屬性如果出現在select子句中的話,它只能出現在聚集函數內部,否則這樣的查詢就是錯誤的。例如:

SELECT AVG(city_id),province_id,city_name
FROM city
GROUP BY province_id;

上述查詢就是錯誤的,因爲city_name沒有出現在group by子句中,但它出現在了select子句中,而且沒有在聚集函數中。這一點很容易理解,在一個以province_id構造的分組中的每一個城市都有不同的city_name,在select子句中且沒有在聚集函數中只能輸出一個元組,那就無法確定選擇哪個city_name作爲輸出,所以SQL不會允許這樣的情況出現。

having子句

有時候,對分組限定條件比對元組限定條件更有用。例如我們只對city關係的平均city_id大於140000的城市感興趣,該條件並不針對單個元組,而是針對group by子句構成的元組。爲表達這樣的查詢,我們使用SQL的having子句。having子句中的謂詞在形成分組後才起作用,因此可以使用聚集函數。用SQL表達查詢如下:

SELECT AVG(city_id),province_id
FROM city
GROUP BY province_id
HAVING AVG(city_id) > 140000;

與select子句的情況類似,任何出現在having子句且沒有被聚集的屬性必須出現在group by子句中,否則查詢就被當作是錯誤的。

包含聚集、group by或having子句的查詢的含義可通過下述操作序列來定義:

  1. 與不帶聚集的查詢情況類似,最先根據from子句來計算出一個關係。
  2. 如果出現了where子句,where子句中的謂詞將應用到from子句的結果關係上。
  3. 如果出現了group by子句,滿足where謂詞的元組通過group by子句形成分組;如果沒有group by子句,滿足where謂詞的整個元組集被當作一個分組。
  4. 如果出現了having子句,它將應用到每個分組上;不滿足having子句謂詞的分組將被拋棄。
  5. select子句利用剩下的分組產生出查詢結果中的元組,即在每個元組上應用聚集函數來得到單個結果元組。

對空值的聚集

聚集函數將根據以下原則處理空值:除了count(*)外所有的聚集函數都忽略輸入集合中的空值。由於空值被忽略,有可能造成參加函數運算的輸入值集合爲空集。規定空集的count運算值爲0,其他所有聚集運算在輸入爲空集的情況下會返回一個空值。

嵌套子查詢

集合成員資格

SQL允許測試元組在關係中的成員資格。連接詞in測試元組是否是集合中的成員,集合是由select子句產生的一組值構成的。連接詞not in測試元組是否不是集合中的成員。

例如找出所有province_id爲130000且first_letter與province_id不爲130000的元組的first_letter有相同值的城市信息,先寫出子查詢:

SELECT first_letter
FROM city
WHERE province_id != 130000;

然後我們需要在子查詢形成的集合中找出與province_id爲130000的城市的first_letter相同的城市,將子查詢嵌入到外部查詢的where子句中,最終查詢語句如下:

SELECT * FROM city
WHERE province_id = 130000
AND first_letter
IN (SELECT first_letter
FROM city
WHERE province_id != 130000);

我們可以用in結構類似的方式去使用not in結構。例如找出province_id爲130000且first_letter與province_id不爲130000的元組的first_letter沒有相同值的城市,SQL如下:

SELECT * FROM city
WHERE province_id = 130000
AND first_letter
NOT IN (SELECT first_letter
FROM city
WHERE province_id != 130000);

in和not in操作符也能用於枚舉集合。

集合的比較

作爲一個說明嵌套子查詢能對集合進行比較的例子,如下查詢:“找出滿足下麪條件的所有城市的city_id,它們的city_id比province_id爲130000的任一city_id要大”,此查詢可以書寫爲:

SELECT DISTINCT c1.city_id 
FROM city c1,city c2
WHERE c1.city_id > c2.city_id
AND c2.province_id = 130000;

SQL提供了另外一種方式書寫上面的查詢。短語“至少比某一個大”在SQL中用>some表示。此結構允許我們用一種更貼近此查詢的文字表達的形式重寫上面的查詢:

SELECT city_id
FROM city
WHERE city_id > SOME(SELECT city_id
                     FROM city
                     WHERE province_id = '130000');

SQL也允許<some,<=some,>=some,=some和<>some的比較。=some等價於in,但<>some並不等價於not in。另外,some與any同義。

現在稍微修改一下查詢,找出滿足下麪條件的所有城市的city_id,它們的city_id比province_id的所有city_id都高。結構>all對應於短語“比所有的都大”,使用該結構,SQL語句如下:

SELECT city_id
FROM city
WHERE city_id > ALL(SELECT city_id
                    FROM city
                    WHERE province_id = 130000);

類似於some,SQL也允許<all,<=all,>=all,=all和<>all的比較。<>all等價於not in,但=all不等價於in。

作爲集合比較的另一個例子,找出平均city_id最高的province_id。我們首先寫一個查詢來找出每個省份的平均city_id,然後把它作爲子查詢嵌套在一個更大的查詢中,以找出那些平均city_id大於等於所有省份的平均city_id的province_id。SQL如下:

SELECT province_id
FROM city
GROUP BY province_id
HAVING AVG(city_id) >= ALL(SELECT AVG(city_id)
                           FROM city
                            GROUP BY province_id);

空關係測試

SQL還有一個特性可測試一個子查詢的結果中是否存在元組。exists結構在作爲參數的子查詢非空時返回true值。使用exists結構,我們可以用另一種方式書寫查詢“找出所有province_id爲130000且first_letter與province_id不爲130000的元組的first_letter有相同值的城市信息”:

SELECT *
FROM city c1
WHERE province_id = 130000
AND EXISTS (SELECT *
            FROM city c2
            WHERE province_id != 130000
            AND c1.first_letter = c2.first_letter);

上述查詢還說明了SQL的一個特性,來自外層查詢的一個相關名稱(別名)可以用在where子句的子查詢中。使用了來自外層查詢相關名稱的子查詢被稱作相關子查詢(correlated subquery)。

在包含了子查詢的查詢中,在相關名稱上可以應用作用域規則。根據此規則,在一個子查詢中只能使用此子查詢本身定義的,或者包含此子查詢的任何查詢中定義的相關名稱。如果一個相關名稱既在子查詢中定義,又在包含該子查詢的查詢中定義,則子查詢中的定義有效。

我們可以用not exists結構測試子查詢結果集中是否不存在元組。

From子句中的子查詢

SQL允許在from子句中使用子查詢表達式。在此採用的觀點是:任何select-from-where表達式返回的結果都是關係,因而可以被插入到另一個select-from-where中任何關係可以出現的位置。

之前介紹having子句時的例子:“找出city關係的平均city_id大於140000的城市的平均city_id”,我們可以不用having子句而使用from子句中使用子查詢的方式:

SELECT province_id,avg_city_id
FROM (SELECT province_id,AVG(city_id) avg_city_id
      FROM city
      GROUP BY province_id) sub
WHERE avg_city_id > 140000;

子查詢的結果屬性可以在外層查詢中使用,需要注意的是,MySQL要求必須給每個子查詢結果關係都給一個名字,即使該名字從來不被引用。

數據庫的修改

刪除

我們只能刪除元組而不能只刪除某些屬性上的值,刪除的語句結構如下:

DELETE FROM r
WHERE P;

其中P代表一個謂詞,r代表一個關係。delete語句首先從r中找出所有使P(t)爲真的元組t,然後把它們從r中刪除。如果省略where子句,則r中所有元組將被刪除。

delete命令只能作用域一個關係。如果我們想從多個關係中刪除元組,必須在每個關係上使用一條delete命令。where子句中得謂詞可以和select命令的where的子句中的謂詞一樣複雜。

插入

要往關係中插入數據,我們可以指定待插入的元組,或者寫一條查詢語句來生成待插入的元組集合。

最簡單的insert語句是單個元組的插入請求。假如我們想向city關係中插入一個城市id爲000000,城市名稱爲“紐約”,province_id爲130000的城市,可寫成

INSERT INTO city
VALUES(000000,'紐約',130000,'ny',0,1);

在此例中,元組屬性值的排列順序和關係模式中屬性排列的順序一致。SQL也允許在insert語句中指定屬性,例如下面的語句與前述語句的功能想。

INSERT INTO city(city_id,province_id,city_name,first_letter,is_hot,state)
VALUES(000000,130000,'紐約','ny',0,1);

有些情況我們可能想在查詢結果的基礎上插入數組,其語句結構如下:

INSERT INTO r1
    SELECT A1,A2,...An
    FROM r2
    WHERE p;

在討論insert語句時我們只考慮了這樣的例子:待插入元組的每個屬性都被賦了值。但有可能待插入元組中只給了模式中部分屬性的值,那麼其餘屬性將被賦空值,用null表示(前提是這些屬性可以爲null,如果有not null約束,插入語句報錯)。

更新

有些情況下,我們希望在不改變整個元組的情況下改變其部分屬性的值。爲達到這一目的,可以使用update語句。例如將所有的城市的city_id*10,SQL語句如下:

UPDATE city
SET city_id = city_id * 10;

上述語句將更新關係中的所有元組。如果想將city_id爲130900的城市名稱改爲“我的家鄉”,語句如下:

UPDATE city
SET city_name = '我的家鄉'
WHERE city_id = 130900;

SQL提供case結構,其功能類似於Java語言中的switch...case...結構。例如我們想對province_id爲130000的所有城市的city_id加一且province_id爲140000的所有城市的city_id減一,使用case結構語句如下:

UPDATE city
SET city_id = CASE province_id
		WHEN 130000 THEN city_id + 1
		WHEN 140000 THEN city_id - 1
		ELSE city_id
	END;

case結構不僅可以用在更新語句中,還可以用在select語句中。例如查詢:“找出所有城市,province_id爲130000的城市顯示爲'河北省',province_id不爲130000的城市顯示爲'其他省'”,SQL語句如下:

SELECT city_name,province_id,
(CASE province_id
 WHEN 130000 THEN '河北省'
 ELSE '其他省'
 END) AS 省份
 FROM city;

查詢結果如圖(下左一)。

case查詢

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