第六十九章. SQL
內容
關系數據模型
關系數據模型公式
在關系數據模型上的操作
SQL 語言
本章介紹隱藏在關系數據庫后面的數學原理.這些內容并不是學習所必要的,因此如果如果你陷在這里或者想直接獲取一些簡單的例子,請隨意跳到下一章閱讀,并且當你有更多的時間和耐心后,隨時歡迎回過頭閱讀這一章.我們認為這些內容是非常有趣的.
本章最初出現在 Stefan Simkovics 的碩士論文的一部分里(Simkovics, 1998)。
SQL 已經成為了最流行的關系查詢語言。SQL”的名稱是Structured Query Language(結構化查詢語言)的縮寫。在 1974 年,Donald Chamberlin 和其他人在 IBM 的研究所定義了語言 SEQUEL (Structured English Query Language)(結構化英語查詢語言)。這種語言在 1974-75 年首先在一種叫 SEUEL-XRM 的 IBM 的原型里面實現。到了 1976-77 年,定義了一種叫 SEQUEL/2 的 SEQUEL 改進版,并且名稱也因此改成 SQL。
在 1977 年,IBM 開發了一種新的叫 System R 的原型語言。 System R 實現了 SEQUEL/2 (現在的 SQL)的很大一部分子集,并且在項目過程中對SQL 做了許多修改。System R 被安裝到了許多用戶節點上,包括 IBM 的節點和許多經過選擇的客戶節點上。多虧了在 System R 那些用戶節點上的成功,IBM 開始開發基于 System R 技術的 SQL 語言的商業產品。
再過了一年,IBM 和許多其他提供商宣布了許多 SQL 產品,例如 SQL/DS(IBM),DB2(IBM),ORACLE(Oracle Corp.),DG/SQL(Data General Corp.),和 SYBASE (Sybase Inc.)。
SQL 現在還是一個官方標準。在 1982 年,美國國家標準局(ANSI)公布了數據庫委員會憲章(Database Committee)X3H2,建議發展一種標準的關系語言。這個建議在 1986 年被批準并且考慮了實際上是 IBM 版本的 SQL。在 1987 年,這個ANSI 標準也被國際標準化組織(ISO)接受為國際標準。這個最初的SQL 版本的標準經常非正式的被稱為"SQL/86"。到了 1989 年,那個最初的標準被擴展了,并且這個新的標準被經常地非正式的稱為 "SQL/89"。同樣在 1989 年,一個相關的標準,發展了稱之為數據庫語言嵌入SQL (Database Language Embedded SQL)(ESQL)。
ISO 和 ANSI 委員會已經在一個大大地擴展了最初的標準的新版本的定義上工作了好幾年,常被非正式的稱之為SQL2 或 SQL/92。這個版本成為一個批準了的標準 - "International Standard ISO/IEC 9075:1992, Database Language SQL" - 在1992 年晚些時候。SQL/92 是人們常說的 "SQL 標準" 時所指的版本。SQL/92 的詳細描述在 Date and Darwen, 1997 里給出。在我們寫作這份文檔的時候,一個非正式地稱為SQL3的新的標準正在制訂。這個新的標準準備把SQL 變成一種旋轉完成(Turing-complete)語言,也就是說,所有可計算的查詢(例如,遞歸查詢)都將是可能的。這是一個非常復雜的任務,因而新標準的完成不會在 1999 年以前。
關系數據模型
如上所述,SQL 是一種關系語言。那意味著它是基于 E.F. Codd 在 1970 年首次發表的關系數據模型。我們將在稍后給出關系模型的正式描述(在 關系數據模型公式里),不過我們先用一個更直觀的視角看看這個模型。
一個關系數據庫是被其用戶看作一個表的集合的數據庫(而且除表之外沒有其他東西)。一個表由行和列組成,每行代表一條記錄,每列代表一個包含在表里面的記錄的屬性。供應商和部件數據庫 演示了一個由三個表組成的數據庫:
SUPPLIER 是存儲供應商數字(SNO),名稱(SNAME)和城市(CITY)的表。
PART 是存儲部件的數字(PNO)名稱(PNAME)和價格(PRICE)的表。
SELLS 存儲關于某個供應商(SNO)銷售的部件(PNO)的信息。它是聯接其他兩個表的結點。
例 69-1. 供應商和部件數據庫
SUPPLIER SNO | SNAME | CITY SELLS SNO | PNO
-----+---------+-------- -----+-----
1 | Smith | London 1 | 1
2 | Jones | Paris 1 | 2
3 | Adams | Vienna 2 | 4
4 | Blake | Rome 3 | 1
3 | 3
4 | 2
PART PNO | PNAME | PRICE 4 | 3
-----+---------+--------- 4 | 4
1 | Screw | 10
2 | Nut | 8
3 | Bolt | 15
4 | Cam | 25
表 PART 和 SUPPLIER 可以看作不同的實體而 SELLS 可以看作是一種特定部件和特定供應商之間的關系。
如我們稍后將看到的,SQL 對表進行操作,就象我們剛才定義的那樣,不過在這之前,我們將先學習關系模型的理論。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
關系數據模型公式
躲在關系模型背后的數學理論是集合理論中的關系理論,它也是一列(數)域的笛卡兒乘積的子集。這樣的集合理論中的關系給出其模型的名稱(不要把它與來自實體關系模型的關系搞混了)。一個(數)域形式上只是一些數值的集合。例如,整數的集合是一個(數)域。同樣長度為 20 的字串和實數的集合也是(數)域的例子。
(數)域 D1, D2, ... Dk 的笛卡兒乘積,標記 D1 × D2 × ... × Dk 就是所有 k元記錄 v1, v2, ... vk 的集合,這里 v1 ∈ D1,v1 ∈ D1,... vk∈ Dk。
例如,假設我們有 k=2,D1={0,1} 和 D2={a,b,c} 那么 D1 × D2 是 {(0,a),(0,b),(0,c),(1,a),(1,b),(1,c)}。
一個關系就是一個或者多個(數)域的笛卡兒乘積的任意子集:R &sube D1 × D2 × ... × Dk。
例如 {(0,a),(0,b),(1,a)} 是一個關系;它實際上是上面提到的 D1 × D2 的子集。
關系的成員叫做記錄。某個笛卡兒乘積 D1 × D2 × ... × Dk 的每個關系都有 k 元,因而我們有一個 k元記錄的集合。
一個關系可以看作一個表(正如我們已經說過的,還記得 供應商和部件數據庫 里,每條記錄被一行代表而每列對應記錄里的一個元素)。賦予列(叫字段/屬性)以名稱導致一個關系設計的定義。
一個關系設計 R 是一個有限屬性 A1,A2,... Ak的集合。對每個屬性 Ai,1 <= i <= k,存在一個(數)域 Di,該屬性的值是從這個(數)域里面取出來的。我們經常把關系設計寫成 R(A1, A2, ... Ak)。
注意:一個關系設計只是某種模板,而一個關系是一個關系設計的實例。關系由記錄組成(因而可以看作是一個表);而關系設計可不能這樣看。
(數)域與數據類型的對比
在上面的段落里我們經常談到(數)域。記住一個(數)域(形式上)只是一個數值的集合(比如,整數或者實數的集合)。在數據庫系統的用語里面,我們常用數據類型代替(數)域。當我們定義一個表的時候,我們必須決定要包括那些屬性。另外我們還要決定屬性數值將存儲為哪種類型的數據。例如表 SUPPLIER 里的 SNAME 的值將存儲為字符串,而 SNO 將被存儲為整數。我們通過賦予每個屬性一種數據類型來定義這些內容。 SNAME 的類型是 VARCHAR(20)(這是 SQL 用于長度 <= 20 的字符串的類型),SNO 的類型將是 INTEGER。在賦予屬性數據類型的同時,我們同樣也為它選擇了一個(數)域。SNAME 的(數)域是所有長度 <= 20 的字符串的集合,SNO 的(數)域是所有整數的集合。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
關系數據模型上的操作
在上一節(Relational Data Model Formalities)里,我們定義了關系模型的數學概念。現在我們知道了用關系數據模型如何存儲數據,但是我們還不知道如何從這些表里面檢索某些東西。例如,某人想知道銷售部件 'Screw' 的所有的供應商的名稱。因此定義了兩種差別相當大的用于表示對關系的操作的符號:
關系代數是一種代數的符號,其中的查詢是通過想關系附加特定的操作符來表示的。
關系微積分是一種邏輯符號,其中的查詢是通過公式表示答案里的記錄必須滿足的某些邏輯約束來表示的。
關系代數
關系代數是 E. F. Codd 于 1972 首先提出的。它包括一個對表進行操作的集合:
SELECT (σ):從關系里面抽取出滿足給定限制條件的記錄。令 R 為一個表,包含一個屬性 A。σA=a(R) = {t ∈ R &mid t(A) = a} 這里 t 表示 R 的一條記錄而 t(A) 表示記錄 t 的屬性 A 的值。
PROJECT (π):從一個關系里面抽取指明的屬性(列)。令 R 為一個包含一個屬性 X 的關系。πX(R) = {t(X) &mid t ∈R},這里 t(X) 表示記錄 t 里的屬性 X 的值。
PRODUCT (×):計算兩個關系的笛卡兒乘積。令R 為含有 k1 個元的表而令 S 為含有 k2 個元的表。R × S 是所有含有 k1 + k2 個元記錄的集合,其前面 k1 個元構成 R 里的一條記錄而后面 k2 個元構成 S 里的一條記錄。
UNION (∪):計算兩個表的集合理論上的聯合。給出表 R 和 S (兩者有相同元/列數), R ∪ S 的聯合就是所有在 R 里面有或 S 里面有或在兩個表里面都有的記錄的集合。
INTERSECT (∩):計算兩個表集合理論上的交集。給出表 R 和 S,R ∩ S 是同時在 R 和 S 里面的記錄的集合。我們同樣要求 R 和 S 擁有相同的元/列數。
DIFFERENCE (- 或 &setmn):計算兩個表的區別的集合。令 R 和 S 還是擁有相同元/列的表。R - S 是在 R 里面但是不在 S 里面的記錄的集合。
JOIN (∏):通過共同屬性聯接兩個表。令 R 為一個有屬性 A,B 和 C 的表,而令 S 為一個有屬性 C,D 和 E 的表。兩個表有一個共同的屬性,屬性 C。R ∏ S = πR.A,R.B,R.C,S.D,S.E(σR.C=S.C(R × S))。我們在這里干了社呢們?首先我們計算笛卡兒乘積 R × S。然后我們選擇那些公共屬性 C 的數值相同(σR.C = S.C)的字段。這時我們擁有一個包含屬性 C 兩次的表,然后我們把重復的列去掉以后得出我們要的結果。
例子 69-2. 一個內部聯接
讓我們看看計算一個聯合需要的各個步驟產生的表。給出下面兩個表:
R A | B | C S C | D | E
---+---+--- ---+---+---
1 | 2 | 3 3 | a | b
4 | 5 | 6 6 | c | d
7 | 8 | 9
首先我們先計算笛卡兒乘積 R × S 并得到:
R x S A | B | R.C | S.C | D | E
---+---+-----+-----+---+---
1 | 2 | 3 | 3 | a | b
1 | 2 | 3 | 6 | c | d
4 | 5 | 6 | 3 | a | b
4 | 5 | 6 | 6 | c | d
7 | 8 | 9 | 3 | a | b
7 | 8 | 9 | 6 | c | d
在選擇 σR.C=S.C(R × S) 后我們得到:
A | B | R.C | S.C | D | E
---+---+-----+-----+---+---
1 | 2 | 3 | 3 | a | b
4 | 5 | 6 | 6 | c | d
刪除重復的列 S.C 我們用下面的操作:πR.A,R.B,R.C,S.D,S.E(σR.C=S.C(R × S)) 將其選出,得到:
A | B | C | D | E
---+---+---+---+---
1 | 2 | 3 | a | b
4 | 5 | 6 | c | d
DIVIDE (÷):令 R 是一個有屬性 A,B,C 和 D 的表,并且令 S 是一個有著屬性 C 和 D 的表。然后我們把除定義為:R ÷ S = {t &mid &forall ts ∈ S &exist tr ∈ R 令 tr(A,B)=t∧tr(C,D)=ts} 這里 tr(x,y) 代表表 R 里只包含元素 x 和 y 的記錄。注意記錄 t 只包含關系 R 里的元素 A 和 B。
給出下面的表
R A | B | C | D S C | D
---+---+---+--- ---+---
a | b | c | d c | d
a | b | e | f e | f
b | c | e | f
e | d | c | d
e | d | e | f
a | b | d | e
R ÷ S 生成
A | B
---+---
a | b
e | d
關于關系代數的更詳細的描述和定義,請參考[Ullman, 1988] 或 [Date, 1994]。
例 69-3. 使用關系代數的一個例子
回憶一下我們闡明的所有可以用語從數據庫里檢索數據的關系操作符。現在我們可以回到前一章的例子里(關系數據模型里的操作),那時我們需要知道銷售部件 Screw 的所有供應商的名稱。現在我們可以用下面的關系代數的操作回答這個問題了:
πSUPPLIER.SNAME(σPART.PNAME='Screw'(SUPPLIER × SELLS × PART))
我們稱這樣的操作是一次查詢。如果我們對我們的例子表(The Suppliers and Parts Database)進行上述查詢計算,我們將獲得下面結果:
SNAME
-------
Smith
Adams
關系微積分
關系微積分基于第一順序邏輯(first order logic)。關系微積分有兩個變種:
(數)域關系微積分(Domain Relational Calculus)(DRC),其變量代表記錄的元素(屬性)。
記錄關系微積分(Tuple Relational Calculus)(TRC),其變量代表記錄。
我們只打算討論記錄關系微積分,因為它是大多數關系語言背后的數學基礎。有關DRC (當然還有 TRC)的詳細討論,參閱 Date, 1994 或 Ullman, 1988。
記錄關系微積分
在 TRC 里使用的查詢是這樣的形式:x(A) &mid F(x),這里 x 是一個記錄變量, A 是屬性的集合而 F 是一個公式。生成的關系包含所有滿足 F(t) 的記錄 t(A)。
如果我們想用 TRC 回答例子 一個使用關系代數的查詢 里的問題,我們可以寫出下面查詢:
{x(SNAME) &mid x ∈ SUPPLIER ∧ /nonumber
&exist y ∈ SELLS &exist z ∈ PART (y(SNO)=x(SNO) ∧ /nonumber
z(PNO)=y(PNO) ∧ /nonumber
z(PNAME)='Screw')} /nonumber
對 供應商和部件數據庫 計算這個查詢同樣得出與 一個使用關系代數的查詢相同的結果。
關系代數與關系微積分的比較
關系代數和關系微積分有著相同的表達能力;也就是說,所有可以用關系代數表達的查詢都可以使用關系微積分來表達,反之亦然。這個結論首先是 E. F. Codd 在 1972 年證明的。這個證明是基于一個算法(“Codd 的歸納算法”),利用這個算法,一個任意的關系微積分的表達式可以歸納為一個語義上相等的關系代數的表達式。關于這些更詳細的討論,請參考 Date, 1994 和 Ullman, 1988。
有時候我們說基于關系微積分的語言比基于關系代數的語言是更 "高級的" 或者 "更描述性的" 語言,因為代數(部分上)說明了操作的過程而微積分把這些過程留給編譯器或者解釋器以決定計算的最有效順序。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
SQL 語言
正如大多數現代的關系語言一樣,SQL 是基于記錄關系微積分的。結果是每個可以用記錄關系微積分表示的查詢(相等地,或者是關系代數),同樣也可以用SQL 表示。不過,SQL 還有一些超出關系代數或者微積分的能力。下面是一個SQL 提供的并非關系代數或者關系微積分的內容的一個列表:
插入,刪除或者更改數據的命令。
算術能力:在 SQL 里,我們可以和比較功能一樣進行算術運算,例如 A < B + 3。要注意 + 或者其他算術操作符從未在關系代數或者關系微積分里面出現過。
分配和打印命令:我們可以打印一條查詢構造的關系以及給一個被計算的關系分配關系名。
聚集函數:象 average,sum,max,等操作符可以應用于一個關系的列以獲取單一的量。
Select(選擇)
SQL 里面最常用的命令是 SELECT 語句,用于檢索數據。語法是:
SELECT [ALL|DISTINCT]
{ * | expr_1 [AS c_alias_1] [, ...
[, expr_k [AS c_alias_k]]]}
FROM table_name_1 [t_alias_1]
[, ... [, table_name_n [t_alias_n]]]
[WHERE condition]
[GROUP BY name_of_attr_i
[,... [, name_of_attr_j]] [HAVING condition]]
[{UNION [ALL] | INTERSECT | EXCEPT} SELECT ...]
[ORDER BY name_of_attr_i [ASC|DESC]
[, ... [, name_of_attr_j [ASC|DESC]]]];
現在我們將通過不同的例子演示 SELECT 語句復雜的語法。用于這些例子的表在 供應商和部件數據庫 里定義。
簡單的 Selects
這里是一些使用 SELECT 語句的簡單例子:
例 69-4. 帶有條件的簡單查詢
要從表 PART 里面把字段 PRICE 大于 10 的所有記錄找出來,我們寫出下面查詢:
SELECT * FROM PART
WHERE PRICE > 10;
然后得到表:
PNO | PNAME | PRICE
-----+---------+--------
3 | Bolt | 15
4 | Cam | 25
在 SELECT語句里使用 "*" 將檢索出表中的所有屬性。如果我們只希望從表 PART 中檢索出屬性 PNAME 和 PRICE,我們使用下面的語句:
SELECT PNAME, PRICE
FROM PART
WHERE PRICE > 10;
這回我們的結果是:
PNAME | PRICE
--------+--------
Bolt | 15
Cam | 25
請注意 SQL 的 SELECT 語句對應關系代數里面的 "projection" (映射),而不是 "selection"(選擇)(參閱 關系代數 獲取詳細信息)。
WHERE 子句里的條件也可以用關鍵字 OR,AND,和 NOT 邏輯的連接起來:
SELECT PNAME, PRICE
FROM PART
WHERE PNAME = 'Bolt' AND
(PRICE = 0 OR PRICE < 15);
這樣將生成下面的結果:
PNAME | PRICE
--------+--------
Bolt | 15
目標列表和 WHERE 子句里可以使用算術操作。例如,如果我們想知道如果我們買兩個部件的話要多少錢,我們可以用下面的查詢:
SELECT PNAME, PRICE * 2 AS DOUBLE
FROM PART
WHERE PRICE * 2 < 50;
這樣我們得到:
PNAME | DOUBLE
--------+---------
Screw | 20
Nut | 16
Bolt | 30
請注意在關鍵字 AS 后面的 DOUBLE 是第二個列的新名字。這個技巧可以用于目標列表里的每個元素,給它們賦予一個在結果列中顯示的新的標題。這個新的標題通常稱為別名。這個別名不能在該查詢的其他地方使用。
Joins(聯接)
下面的例子顯示了 SQL 里是如何實現聯接的。
要在共同的屬性上聯合三個表 SUPPLIER,PART 和 SELLS,我們通常使用下面的語句:
SELECT S.SNAME, P.PNAME
FROM SUPPLIER S, PART P, SELLS SE
WHERE S.SNO = SE.SNO AND
P.PNO = SE.PNO;
而我們得到的結果是:
SNAME | PNAME
-------+-------
Smith | Screw
Smith | Nut
Jones | Cam
Adams | Screw
Adams | Bolt
Blake | Nut
Blake | Bolt
Blake | Cam
在 FROM 子句里,我們為每個關系使用了一個別名,因為在這些關系間有著公共的命名屬性(SNO 和 PNO)。現在我們可以區分不同表的公共命名屬性,只需要簡單的用每個關系的別名加上個點做前綴就行了。聯合是用與 一個內部聯接 里顯示的同樣的方法計算的。首先算出笛卡兒乘積 SUPPLIER × PART × SELLS。然后選出那些滿足 WHERE 子句里給出的條件的記錄(也就是說,公共命名屬性的值必須相等)。最后我們映射出除 S.SNAME 和 P.PNAME 外的所有屬性。
聚集操作符
SQL 提供以一個屬性的名稱為參數的聚集操作符(如,AVG,COUNT,SUM,MIN,MAX)。聚集操作符的結果是對整個表中的所有聲明的屬性(列)的值進行計算的結果。如果在一個查詢里面聲明了組,那么這種(聚集)計算將只發生在一個組的值上(見下節)。
例 69-5. 聚集
如果我們想知道表 PART 里面所有部件的平均價格,我們可以使用下面查詢:
SELECT AVG(PRICE) AS AVG_PRICE
FROM PART;
結果是:
AVG_PRICE
-----------
14.5
如果我們想知道在表 PART 里面存儲了多少部件,我們可以使用語句:
SELECT COUNT(PNO)
FROM PART;
得到:
COUNT
-------
4
分組聚集
SQL 允許我們把一個表里面的記錄分成組。然后上面描述的聚集操作符可以應用與這些組上(也就是說,聚集操作符的值不再是對所有聲明的列的值進行操作,而是對一個組的所有值進行操作。這樣聚集函數是為每個組獨立地進行計算的。)
對記錄的分組是通過關鍵字 GROUP BY 實現的, GROUP BY 后面跟著一個定義組的構成的屬性列表。如果我們使用語句 GROUP BY A1, &tdot, Ak,我們把關系分成了組,這樣如果兩條記錄在(也只是在)所有屬性 A1, &tdot, Ak 上達成一致,它們是同一組的。
例 69-6. 聚集
如果我們想知道每個供應商銷售多少個部件,我們可以這樣寫查詢:
SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
FROM SUPPLIER S, SELLS SE
WHERE S.SNO = SE.SNO
GROUP BY S.SNO, S.SNAME;
得到:
SNO | SNAME | COUNT
-----+-------+-------
1 | Smith | 2
2 | Jones | 1
3 | Adams | 2
4 | Blake | 3
現在我們看一看發生了什么事情。首先生成表 SUPPLIER 和 SELLS 的聯接:
S.SNO | S.SNAME | SE.PNO
-------+---------+--------
1 | Smith | 1
1 | Smith | 2
2 | Jones | 4
3 | Adams | 1
3 | Adams | 3
4 | Blake | 2
4 | Blake | 3
4 | Blake | 4
然后我們把那些屬性 S.SNO 和 S.SNAME 相同的記錄放在組中:
S.SNO | S.SNAME | SE.PNO
-------+---------+--------
1 | Smith | 1
| 2
--------------------------
2 | Jones | 4
--------------------------
3 | Adams | 1
| 3
--------------------------
4 | Blake | 2
| 3
| 4
在我們的例子里,我們有四個組并且現在我們可以對每個組應用聚集操作符 COUNT,生成上面給出的查詢的最終結果。
請注意如果要讓一個使用 GROUP BY 和聚集操作符的查詢的結果有意義,那么用于分組的屬性也必須出現在目標列表中。所有沒有在 GROUP BY 子句里面出現的屬性都只能通過使用聚集函數來選擇。另一方面,你不能對出現在 GROUP BY 子句里面的屬性使用聚集函數。
Having(具有)
HAVING 子句工作起來非常象 WHERE 子句,只用于對那些滿足 HAVING 子句里面給出的條件的組進行計算。在 HAVING 子句里面允許使用的表達式必須包括聚集函數。每個表達式只能使用屬于 WHERE 子句的簡單屬性。另一方面,每個聚集函數里面的表達式必須放到 HAVING 子句里面。
例 69-7. Having
如果我們想知道那些銷售超過一個部件的供應商,使用下面查詢:
SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
FROM SUPPLIER S, SELLS SE
WHERE S.SNO = SE.SNO
GROUP BY S.SNO, S.SNAME
HAVING COUNT(SE.PNO) > 1;
得到:
SNO | SNAME | COUNT
-----+-------+-------
1 | Smith | 2
3 | Adams | 2
4 | Blake | 3
子查詢
在 WHERE 和 HAVING 子句里,允許在任何要產生數值的地方使用子查詢(子選擇)。這種情況下,該值來自首先對子查詢的計算。子查詢的使用擴展了SQL 的表達能力。
例 69-8. 子查詢
如果我們想知道所有比名為 'Screw' 的部件貴的部件,我們可以用下面的查詢:
SELECT *
FROM PART
WHERE PRICE > (SELECT PRICE FROM PART
WHERE PNAME='Screw');
結果是:
PNO | PNAME | PRICE
-----+---------+--------
3 | Bolt | 15
4 | Cam | 25
當我們檢查上面的查詢時會發現出現了兩次 SELECT 關鍵字。第一個在查詢的開頭 - 我們將稱之為外層 SELECT - 而另一個在 WHERE 子句里面,成為一個嵌入的查詢 - 我們將稱之為內層 SELECT。對外層 SELECT 的每條記錄都必須先計算內層 SELECT。在完成所有計算之后,我們得知名為 'Screw' 部件的記錄的價格,然后我們就可以檢查那些價格更貴的記錄了。
如果我們想知道那些不銷售任何部件的供應商(比如說,我們想把這些供應商從數據庫中刪除),我們用:
SELECT *
FROM SUPPLIER S
WHERE NOT EXISTS
(SELECT * FROM SELLS SE
WHERE SE.SNO = S.SNO);
在我們的例子里,結果列將是空的,因為每個供應商至少銷售一個部件。請注意我們在 WHERE 子句的內層 SELECT 里使用了來自外層 SELECT 的 S.SNO。正如前面所說的,子查詢為每個外層查詢計算一次,也就是說,S.SNO 的值總是從外層 SELECT 的實際記錄中取得的。
Union, Intersect, Except(聯合,相交,相異)
這些操作符分別計算兩個子查詢產生的記錄的聯合,相交和集合理論里的相異。
例 69-9. Union, Intersect, Except
下面的例子是 UNION 的例子:
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNAME = 'Jones'
UNION
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNAME = 'Adams';
產生結果:
SNO | SNAME | CITY
-----+-------+--------
2 | Jones | Paris
3 | Adams | Vienna
下面是相交( INTERSECT)的例子:
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO > 1
INTERSECT
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO > 2;
產生結果:
SNO | SNAME | CITY
-----+-------+--------
2 | Jones | Paris
The only tuple returned by both parts of the query is the one having $SNO=2$.(兩個查詢都會返回的記錄是那條 $SNO=2$ 的)
最后是一個 EXCEPT 的例子:
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO > 1
EXCEPT
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO > 3;
結果是:
SNO | SNAME | CITY
-----+-------+--------
2 | Jones | Paris
3 | Adams | Vienna
數據定義
在 SQL 語言里包含一套用于數據定義的命令。
Create Table
數據定義的最基本的命令是創建一個新關系(新表)的命令。CREATE TABLE 命令的語法如下:
CREATE TABLE table_name
(name_of_attr_1 type_of_attr_1
[, name_of_attr_2 type_of_attr_2
[, ...]]);
例 69-10. 創建表
要創建 供應商和部件數據庫 里的表,要使用下面的 SQL 語句:
CREATE TABLE SUPPLIER
(SNO INTEGER,
SNAME VARCHAR(20),
CITY VARCHAR(20));
CREATE TABLE PART
(PNO INTEGER,
PNAME VARCHAR(20),
PRICE DECIMAL(4 , 2));
CREATE TABLE SELLS
(SNO INTEGER,
PNO INTEGER);
SQL 里的數據類型
下面是 SQL 一些數據類型的列表:
INTEGER: 有符號全長二進制整數(31位精度)。
SMALLINT: 有符號半長二進制整數(15位精度)。
DECIMAL (p[,q]): 有符號封裝的十進制小數,共 p 位數,并假設有 q 位數在小數點右邊。(15 ≥ p ≥ qq ≥ 0)。如果省略 q,則認為是 0。
FLOAT: 有符號雙字浮點數。
CHAR(n): 長度為 n 的定長字符串。
VARCHAR(n): 最大長度為 n 的變長字符串。
Create Index
索引用于加速對關系的訪問。如果關系 R 有一個建立在屬性 A 上的索引,那么我們檢索滿足 t(A) = a 的所有記錄 t 用的時間和這樣的記錄數 t 成比例,而不是與 R 的大小成比例。
在 SQL 里創建一個索引,使用 CREATE INDEX 命令。語法是:
CREATE INDEX index_name
ON table_name ( name_of_attribute );
例 69-11. 創建索引
要在關系 SUPPLIER 的屬性 SNAME 上創建一個名為 I 的索引,我們使用下面語法:
CREATE INDEX I
ON SUPPLIER (SNAME);
所創建的索引是自動維護的,也就是說,當向關系 SUPPLIER 插入新的記錄時,索引 I 相應做調節。請注意有索引存在時,用戶能感覺到的唯一的變化就是速度的提升。
Create View(創建視圖)
一個視圖可以看作一個虛擬表,也就是說,數據庫里的一個物理上不存在的,但是用戶看起來卻存在的表。與之相比,當我們談到一個基本表時,則是在物理存儲中的確物理地存放著表中每一行的內容。
視圖沒有它們自身的,物理上分開的,可區分的存儲區。實際上,系統把視圖的定義(也就是說,為物化試圖應如何訪問物理上存儲在基本表中內容的規則)存放在系統表里的某個地方(參閱 系統表)。關于實現視圖的不同技巧,請參考 SIM98。
在 SQL 里,使用 CREATE VIEW 命令定義視圖。語法是:
CREATE VIEW view_name
AS select_stmt
這里 select_stmt 是一個與定義在 Select 里一樣的有效選擇語句。請注意在視圖創建時并不執行 select_stmt。它只是存儲在系統表里,當對視圖進行查詢時將執行。
假設給出下面的視圖定義(我們再次使用來自 供應商和部件數據庫 里的表):
CREATE VIEW London_Suppliers
AS SELECT S.SNAME, P.PNAME
FROM SUPPLIER S, PART P, SELLS SE
WHERE S.SNO = SE.SNO AND
P.PNO = SE.PNO AND
S.CITY = 'London';
現在我們可以把這個虛擬的關系 London_Suppliers 當作是另外一個基本表:
SELECT *
FROM London_Suppliers
WHERE P.PNAME = 'Screw';
將返回下面的表:
SNAME | PNAME
-------+-------
Smith | Screw
要計算這個結果,數據庫系統必須先對基本表 SUPPLIER,SELLS 和 PART 先進行一次隱藏的訪問。它是通過對這些基本表運行該視圖定義里面給出的查詢實現隱藏訪問的。然后,余下的附加條件(在查詢里給出的對視圖的條件)就可以應用上來,最后獲取結果表。
Drop Table, Drop Index, Drop View
要刪除表(包括該表存儲的所有記錄),使用 DROP TABLE 命令:
DROP TABLE table_name;
要刪除 SUPPLIER 表,使用下面語句:
DROP TABLE SUPPLIER;
DROP INDEX 命令用于刪除一個索引:
DROP INDEX index_name;
最后用 DROP VIEW 刪除一個給出的視圖:
DROP VIEW view_name;
數據操作
Insert Into(插入)
一旦表創建完成(參閱 Create Table),就可以用命令 INSERT INTO 向里面填充記錄。語法是:
INSERT INTO table_name (name_of_attr_1
[, name_of_attr_2 [,...]])
VALUES (val_attr_1
[, val_attr_2 [, ...]]);
要向關系 SUPPLIER 中插入第一條記錄(來自 供應商和部件數據庫),我們使用下面語句:
INSERT INTO SUPPLIER (SNO, SNAME, CITY)
VALUES (1, 'Smith', 'London');
要向關系 SELLS 插入第一條記錄,我們用:
INSERT INTO SELLS (SNO, PNO)
VALUES (1, 1);
Update(更新)
要修改記錄中的一個或者多個屬性的值,使用 UPDATE 命令。語法是:
UPDATE table_name
SET name_of_attr_1 = value_1
[, ... [, name_of_attr_k = value_k]]
WHERE condition;
要該表關系 PART 中部件 'Screw' 的屬性 PRICE 的值,我們使用:
UPDATE PART
SET PRICE = 15
WHERE PNAME = 'Screw';
記錄名為 'Screw' 的屬性 PRICE 的新值現在是 15。
Delete
要從一個表中刪除一條記錄,使用 DELETE FROM 命令。語法是:
DELETE FROM table_name
WHERE condition;
要刪除 SUPPLIER 表中名為 'Smith' 的供應商,使用下面的語句:
DELETE FROM SUPPLIER
WHERE SNAME = 'Smith';
系統表
在所有 SQL 數據庫系統里面,系統表都被用于跟蹤表,視圖索引等在數據庫中定義的東西。這些系統表可以象普通關系一樣檢索。例如,有一個系統表用于視圖的定義。這個表存儲來自視圖定義的查詢。每當對視圖進行查詢時,系統在處理用戶查詢之前首先從該表中取出視圖定義查詢并且執行該查詢(參閱 Simkovics, 1998 獲取更詳細的描述)。關于系統表的更多信息,請參考 Date, 1994 。
嵌入的 SQL
在這一節里,我們將勾勒如何把 SQL 嵌入到一個宿主語言里(例如,C)。我們需要從一種宿主語言里使用SQL 的原因主要有兩個:
有一些查詢不可能用純 SQL 表示(比如,遞歸查詢)。要執行這樣的查詢,我們需要一種比SQL 更有表達能力的宿主語言。
我們只是希望能從用宿主語言寫的應用里面訪問數據庫(例如,一個圖形用戶界面的門票預定系統可能用 C 寫,而余票狀況存儲在一個可以用嵌入 SQL 訪問的數據庫里面)。
一個在宿主語言里面使用嵌入 SQL 的程序包含宿主語言的語句和嵌入 SQL(ESQL)的語句。每條ESQL 語句以關鍵字 EXEC SQL 開始。ESQL 語句被一個預編譯器(它通常向源文件中插入庫過程的調用以執行各種SQL 命令)轉換成宿主語言。
當我們觀察例子 Select 時,我們認識到一個查詢的結果大多是記錄的集合。大多數宿主語言不是為集合而設計的,因此我們需要一個機制用來訪問一條 SELECT 語句返回結果集中的每一條記錄。這個機制可以通過定義一個游標來實現。之后我們就可以使用 FETCH 命令檢索一條記錄并把游標定義到下一條記錄。
關于嵌入 SQL 的詳細討論,請參考 Date and Darwen, 1997, Date, 1994,或 Ullman, 1988。
--------------------------------------------------------------------------------