1. SQL 簡介
SQL 的目標(biāo)
理想情況下,數(shù)據(jù)庫語言應(yīng)允許用戶:
- 建立數(shù)據(jù)庫和關(guān)系結(jié)構(gòu)
- 完成基本數(shù)據(jù)管理任務(wù)淤井,諸如關(guān)系中數(shù)據(jù)的插入聊闯、修改和刪除工猜。
- 完成簡單或復(fù)雜的查詢。
數(shù)據(jù)庫語言必須功能豐富菱蔬、結(jié)構(gòu)簡潔篷帅、易學(xué)易用史侣。另外,語言必須易于移植魏身,符合公認(rèn)的標(biāo)準(zhǔn)惊橱,這樣當(dāng)我們更換到不同的 DBMS 時(shí),仍可以使用相同的命令和語法結(jié)構(gòu)箭昵。SQL 語言滿足這些要求税朴。
SQL 語言是 面向轉(zhuǎn)換語言 的例子,它將輸入關(guān)系轉(zhuǎn)換為所需要的輸出關(guān)系家制。作為語言正林,國際標(biāo)準(zhǔn)化組織(ISO)發(fā)布的 SQL 標(biāo)準(zhǔn)包括兩個(gè)主要部分:
- 數(shù)據(jù)定義語言(Data Definition Language,DDL)用于定義數(shù)據(jù)庫結(jié)構(gòu)和數(shù)據(jù)的訪問控制颤殴。
- 數(shù)據(jù)操作語言(Data Manipulation Language觅廓,DML)用于檢索和更新數(shù)據(jù)。
SQL-1999 出現(xiàn)以前涵但,SQL 僅包括數(shù)據(jù)定義和數(shù)據(jù)操作命令杈绸,不包括控制流命令,如 IF...THEN...ELSE矮瘟、GO TO 或 DO...WHILE瞳脓。這些命令的實(shí)現(xiàn)必須用編程語言或任務(wù)控制語言或由用戶交互決定。由于缺乏計(jì)算完備性芥永,僅能用以下兩種方式使用 SQL篡殷。一種方法是在終端交互的輸入 SQL 語句钝吮。另一種方法是將 SQL 語句嵌入過程化語言中埋涧。
SQL 是相對(duì)易學(xué)的語言:
- 非過程化語言:用戶只需描述所需的信息,不需給出獲取該信息的具體過程奇瘦。換句話說 SQL 不需要指定數(shù)據(jù)的訪問方法棘催。
- SQL 和大多數(shù)現(xiàn)代語言一樣,是無格式的耳标,這意味著語句的每一部分不必固定在屏幕上的特定位置醇坝。
- SQL 命令由標(biāo)準(zhǔn)英語單詞組成,如 CREATE TABLE次坡、INSERT呼猪、SELECT 等。例如:
CRREATE TABLE Staff (staffNo VARCHAR(5), lName VARCHAR(15), salary DECIMAL(7, 2);
INSERT INTO Staff VALUES ('SG16', 'Brown', 8300);
SELECT staffNo, lName, salary FROM Staff WHERE salary > 10000;
- SQL 能被數(shù)據(jù)庫管理員砸琅、管理人員宋距、應(yīng)用程序開發(fā)者和各類終端用戶廣泛使用。
目前 SQL 語言已有國際標(biāo)準(zhǔn)症脂,它已成為定義和操作關(guān)系數(shù)據(jù)庫名義上和事實(shí)上的標(biāo)準(zhǔn)(ISO谚赎,1992淫僻,2011a)。
SQL 的歷史
(略)
SQL 的重要性
(略)
術(shù)語
ISO 組織公布的 SQL 標(biāo)準(zhǔn)并未使用正式術(shù)語壶唤,如關(guān)系雳灵,屬性和元組,而是采用表闸盔、列和行這樣的術(shù)語悯辙。通常使用 ISO 術(shù)語表示 SQL 語句。注意蕾殴,SQL 并不嚴(yán)格支持前面給出的關(guān)系模型定義笑撞。例如,SQL 允許 SELECT 語句產(chǎn)生的結(jié)果表中包含重復(fù)行钓觉,它還強(qiáng)調(diào)列的順序茴肥,并且允許用戶對(duì)結(jié)果表中的行進(jìn)行排序。
2. 書寫 SQL 命令
SQL 語句包括 保留字 和 用戶自定義字荡灾。保留字是 SQL 語言的固定部分瓤狐,有固定的含義。保留字必須準(zhǔn)確拼寫批幌,并且不能跨行拼寫础锐。用戶自定義字由用戶自己定義(根據(jù)一定的語法規(guī)則),用于表示表荧缘、列皆警、視圖和索引等數(shù)據(jù)庫對(duì)象的名稱。語句中的其他字也是根據(jù)一定的語法規(guī)則定義的截粗。雖然標(biāo)準(zhǔn)并沒有要求信姓,但 SQL 的許多種實(shí)現(xiàn)版本都要求句子終結(jié)符來標(biāo)識(shí) SQL 語句的結(jié)束(通常用 “ ; ”)。
SQL 語句中的多數(shù)組成部分是 不區(qū)分大小寫 的绸罗,即字母用大寫或小寫均可意推。唯一的例外是字符數(shù)據(jù)常量必須與其在數(shù)據(jù)庫中已經(jīng)存在的大小寫形式一致。舉例說明珊蟀,若某人的姓用 “SMITH” 存儲(chǔ)菊值,而以字符串 “Smith” 查詢,將查詢不到結(jié)果育灸。
SQL 語言格式雖然比較自由腻窒,但為了增加 SQL 語句和語句集的可讀性,可采用縮進(jìn)和下劃線磅崭。例如:
- 語句中每一子句在新的一行開始書寫儿子。
- 每一子句與其他子句的開始字符處在同一列上。
- 如果子句由幾個(gè)部分組成绽诚,則它們應(yīng)當(dāng)分別出現(xiàn)在不同的行典徊,并在子句開始處使用縮進(jìn)表明這種關(guān)系杭煎。
在下文中,將用擴(kuò)展的巴克斯范式(Backus Naur Form卒落,BNF)定義 SQL 語句:
- 大寫字母用于表示保留字羡铲,必須準(zhǔn)確拼寫启绰。
- 小寫字母用于表示用戶自定義字啥么。
- 豎線(|)表示從選項(xiàng)中進(jìn)行 選擇矛缨,例如 a | b | c扯饶。
- 大括號(hào)表示 所需元素,例如 {a}粉怕。
- 中括號(hào)表示 可選元素吼句,例如 [a]根资。
- 省略號(hào)(...)表示某一項(xiàng) 可選擇重復(fù) 零到多次费坊。
例如:{a | b} ( ,c...)
意思是 a 或 b 后緊跟著用逗號(hào)分開的零個(gè)或多個(gè) c倒槐。
實(shí)際上,DDL 語句用于建立數(shù)據(jù)庫結(jié)構(gòu)(即表)和訪問機(jī)制(即每個(gè)用戶能合法訪問什么)附井,而 DML 語句用于查詢和維護(hù)表讨越。但這里先講 DML 語句,這是為了說明 DML 語句對(duì)普通用戶更重要永毅。
3. 數(shù)據(jù)操作
SQL DML 語句有以下幾種:
- SELECT:用于查詢數(shù)據(jù)庫中的數(shù)據(jù)把跨。
- INSERT:用于將數(shù)據(jù)插入表中。
- UPDATE:用于更新表中的數(shù)據(jù)沼死。
- DELETE:用于刪除表中的數(shù)據(jù)着逐。
由于 SELECT 語句比較復(fù)雜,而其他 DML 語句相對(duì)簡單意蛀,這里將用大部分篇幅分析 SELECT 語句和它的各種形式耸别,首先是簡單的查詢,隨后增加排序浸间、分組太雨、聚集和涉及多個(gè)表的復(fù)雜查詢吟榴。最后討論 INSERT魁蒜、UPDATE 和 DELETE 語句。
使用 DreamHome 數(shù)據(jù)庫來說明 SQL 語句吩翻,包括下面各表:
relation | property |
---|---|
Branch | (<u>branchNo</u>, street, city, postcode) |
Staff | (<u>saffNo</u>, fName, lName, position, sex, DOB, salary, branchNo) |
PropertyForRent | (<u>propertyNo</u>, street, city, postcode, type, rooms, rent, ownerNo, staffNo, branchNo) |
Client | (<u>clientNo</u>, fName, lName, telNo, prefType, maxRent, eMail) |
PrivateOwner | (<u>ownerNo</u>, fName, lName, address, telNo, eMail, password) |
Viewing | (<u>clientNo</u>, <u>propertyNo</u>, viewDate, comment) |
常量
討論 SQL DML 之前兜看,很有必要理解常量的概念。常量是指 SQL 語句中使用的 不變量狭瞎。不同的數(shù)據(jù)類型有不同的常量形式细移,簡單來說,常量可以分為用引號(hào)引起來的和不用引號(hào)的熊锭。所有非數(shù)值型數(shù)據(jù)必須用單引號(hào)引起來弧轧,而所有數(shù)值型數(shù)據(jù)一定 不能 使用引號(hào)雪侥。例如,可以使用常量將數(shù)據(jù)插入表中:
INSERT INTO PropertyForRent(propertyNo, street, city, postcode, type, rooms, rent, ownerNo, staffNo, staffNo, branchNo)
VALUES ('PA14', '16 Holhead', 'Aberdeen', 'AB7 5SU', 'House', 6, 650.00, 'CO46', 'SA9', 'B007');
列 rooms 的值是整數(shù)精绎,列 rent 的值是實(shí)數(shù)速缨,它們不能用引號(hào)引起來,其他列的值均為字符串代乃,必須使用引號(hào)旬牲。
簡單查詢
SELECT 語句用于檢索并顯示一個(gè)或多個(gè)數(shù)據(jù)庫表中的數(shù)據(jù)。它功能強(qiáng)大搁吓,可以用一個(gè)語句完成關(guān)系代數(shù)中選擇原茅、連接和投影操作。SELECT 也是 SQL 命令中使用頻率最高的語句堕仔,其形式如下:
SELECT [DISTINCT | ALL] {* | [columnExpression [AS newName]][,...]}
FROM TableName [alias][,...]
[WHERE condition]
[GROUP BY columnList][HAVING condition]
[ORDER BY columnList]
columnExpression 為一列名稱或表達(dá)式擂橘,TableName 給出欲訪問數(shù)據(jù)庫中的表或視圖的名稱,alias 是可選用的 TableName 的簡稱摩骨。SELECT 語句處理過程順序如下:
FROM 給出將用到的表
WHERE 過濾滿足條件的行
GROUP BY 將具有相同屬性值得行分成組
HAVING 過濾滿足條件的組
SELECT 指定查詢結(jié)果中出現(xiàn)的列
ORDER BY 指定查詢結(jié)果的順序
SELECT 語句中子句的順序不能改變贝室,僅有最開始的兩個(gè)子句 SELECT 和 FROM 是必需的,其余子句均為可選擇的仿吞。SELECT 操作為封閉的滑频,即查詢表的結(jié)果將用另一張表顯示。該語句有多種變形唤冈,正像剛才解釋的那樣峡迷。
檢索所有的行
例 6.1:檢索所有的列和所有的行:
列舉所有員工的情況。
因?yàn)楸敬尾樵儫o條件限制你虹,所以省略 WHERE 子句绘搞,查詢所有的列,語句如下:
SELECT staffNo, fName, lName, position, sex, DOB, salary, branchNo
FROM Staff;
因?yàn)?SQL 語句要檢索所有的列傅物,所以一個(gè)簡便的表達(dá)方式是使用星號(hào) “ * ” 代替 “所有列” 的名稱夯辖。等效語句如下:
SELECT *
FROM Staff;
例 6.2:從所有行中檢索指定的列:
生成所有員工的工資表,只包括員工編號(hào)董饰、姓名及工資蒿褂。
SELECT staffNo, fName, lName, salary
FROM Staff;
例 6.3:使用 DISTINCT:
列出被查看過的所有房產(chǎn)的編號(hào)。
SELECT propertyNo
FROM Viewing;
注意卒暂,因?yàn)橥环慨a(chǎn)可以被查看多次啄栓,所以結(jié)果中很可能會(huì)有重復(fù)值,SELECT 語句不會(huì)像關(guān)系代數(shù)的投影操作那樣自動(dòng)消除重復(fù)也祠。用保留字 DISTINCT 可消除重復(fù)昙楚。下面是使用 DISTINCT 的版本:
SELECT DISTINCT propertyNo
FROM Viewing;
例 6.4:計(jì)算字段:
生成所有員工的月工資表,包括員工編號(hào)诈嘿、姓名和工資堪旧。
SELECT staffNo, fName, lName, salary/12
FROM Staff;
給查詢類似于例 6.2削葱,不同之處在于需要查詢的是每月的工資。查詢結(jié)果可以通過簡單的將年薪除以 12 得到淳梦。
本例是使用 計(jì)算字段(有時(shí)稱為 導(dǎo)出字段)的例子佩耳。通常,使用計(jì)算字段時(shí)谭跨,在 SELECT 列表中給出 SQL 表達(dá)式干厚,包括加、減螃宙、乘蛮瞄、除運(yùn)算。另外谆扎,可以使用括號(hào)來建立復(fù)雜的表達(dá)式挂捅。表中可有多個(gè)使用計(jì)算字段的列。而且堂湖,算數(shù)表達(dá)式所引用的列必須是數(shù)字類型闲先。
該查詢結(jié)果表中第 4 列就是輸出列 col4。通常結(jié)果表的列名應(yīng)當(dāng)和用于檢索的數(shù)據(jù)庫的列名相對(duì)應(yīng)无蜂。但是本例這種情況下伺糠,SQL 并不知道如何標(biāo)識(shí)列。慣用方法是根據(jù)列在表中的位置來命名(例如斥季,col4)训桶。在一些方言中,SELECT 列表中列名為空白或者用表達(dá)式寫入酣倾。ISO 標(biāo)準(zhǔn)允許用 AS 子句為列命名舵揭。前面的例子可以用如下方式書寫:
SELECT staffNo, fName, lName, salary/12 AS monthlySalary
FROM Staff;
這樣,查詢結(jié)果中的列將是 monthlySalary 而不是 col4躁锡。
行選擇(WHERE 子句)
前面例子用 SELECT 語句檢索表中所有的行午绳。事實(shí)上,我們經(jīng)常需要限制僅檢索某些行映之,這時(shí)可用 WHERE 子句實(shí)現(xiàn)拦焚,包括關(guān)鍵字 WHERE 和其后給定的用于檢索行的查詢條件。五個(gè)基本的條件運(yùn)算(ISO術(shù)語中的謂詞)如下:
- 比較(comparison):比較兩個(gè)表達(dá)式的值惕医。
- 范圍(range):測試表達(dá)式的值是否在指定的范圍中耕漱。
- 成員關(guān)系(set membership):測試表達(dá)式的值是否在某一值集合內(nèi)算色。
- 模式匹配(pattern match):測試字符串是否與指定模式相匹配抬伺。
- 空(null):測試列是否為空(未知)值。
WHERE 子句等價(jià)于前文討論的關(guān)系代數(shù)的選擇操作≡置危現(xiàn)列出各種查找條件的例子峡钓。
例6.5 比較運(yùn)算作為查找條件:
列出工資高于 10000 英鎊的所有員工妓笙。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary > 10000;
其中,表是 Staff能岩,謂詞是 salary > 10000寞宫,查詢結(jié)果產(chǎn)生一個(gè)工資高于 10000 英鎊的所有員工的列表。
SQL 語句中可用的比較運(yùn)算符如下:
符號(hào) | 意義 |
---|---|
= | 等于 |
<> | 不等于(ISO標(biāo)準(zhǔn)) |
!= | 不等于(某些方言這樣用) |
< | 小于 |
<= | 小于或等于 |
> | 大于 |
>= | 大于或等于 |
復(fù)雜的謂詞可由邏輯運(yùn)算符 AND拉鹃、OR 和 NOT 產(chǎn)生辈赋,必要或期望時(shí)可用括號(hào)表示計(jì)算順序。計(jì)值條件表達(dá)式的規(guī)則如下:
- 計(jì)值順序由左至右膏燕。
- 首先計(jì)算括號(hào)中子表達(dá)式的值钥屈。
- NOT 優(yōu)先于 AND 和 OR。
- AND 優(yōu)先于 OR坝辫。
可運(yùn)用括號(hào)消除歧義篷就。
例 6.6 復(fù)合比較運(yùn)算作為查找條件:
列出位于倫敦或格拉斯哥的所有分公司的地址。
SELECT *
FROM Branch
WHERE city = 'London' OR city = 'Glasgow';
邏輯運(yùn)算符 OR 用于 WHERE 子句中近忙,用于查找倫敦(city = 'London')或格拉斯哥(city = 'Glasgow')的分公司竭业。
例 6.7 范圍作為查找條件(BETWEEN / NOT BETWEEN):
列出工資在 20000 英鎊和 30000 英鎊之間的所有員工。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary BETWEEN 20000 AND 30000;
BETWEEN 測試包括范圍的端點(diǎn)及舍,所以查詢結(jié)果包括工資為 20000 英鎊和 30000 英鎊的職工未辆。BETWEEN 測試并不能增強(qiáng) SQL 的功能,因?yàn)橥ㄟ^使用兩個(gè)比較表達(dá)式也可以完成相同的功能锯玛。BETWEEN 只是簡化了范圍運(yùn)算條件的表達(dá)鼎姐。
例 6.8 集合成員測試作為查找條件(IN / NOT IN):
列出所有的經(jīng)理和主管。
SELECT staffNo, fName, lName, position
FROM Staff
WHERE position IN ('Manager', 'Supervisor');
集合成員資格測試(IN)用于測試數(shù)據(jù)是否與值表中的某一值相匹配更振,本例中就是 “Manager” 或 “Supervisor”炕桨。
像 BETWEEN 一樣,IN 測試并不能增強(qiáng) SQL 的表達(dá)功能肯腕。上面的查詢可以如下表達(dá):
SELECT staffNo, fName, lName, position
FROM Staff
WHERE position = 'Manager' OR position = 'Supervisor';
但是献宫,IN 測試提供了更加有效地查詢條件表達(dá)方式,特別是集合中包括多個(gè)值的時(shí)候实撒。
例 6.9 模式匹配作為查找條件(LIKE / NOT LIKE):
找出其地址中含有字符串 “Glasgow” 的所有業(yè)主姊途。
該查詢是從表 PrivateOwner 中查詢地址中包括字符串 “Glasgow” 的行知态。SQL 有兩種特殊的模式匹配符號(hào):
- %:百分號(hào)表示零或多個(gè)字符序列(通配符)捷兰。
- _:下劃線表示任意單個(gè)字符。
模式匹配中還有其他字符负敏,例如:
-
address LIKE 'H%'
意味著字符串第一個(gè)字符必須是 H贡茅,對(duì)其他字符不做限制。 -
address LIKE 'H___'
意味著字符串正好有 4 個(gè)字符,第一個(gè)字符為 H顶考。 -
address LIKE '%e'
意味著一個(gè)字符序列赁还,長度最小為1,最后一個(gè)字符為 e驹沿。 -
address LIKE '%Glasgow%'
意味著一個(gè)包含字符串 Glasgow 的任意長度序列艘策。 -
address NOT LIKE 'H%'
意味著字符串第一個(gè)字符不能為 H。
如果查找的字符串本身包含上述模式匹配字符渊季,則可用轉(zhuǎn)義字符朋蔫。例如,匹配字符串 '15%' 用下列謂詞:
LIKE '15#%' ESCAPE '#'
利用 SQL 的模式匹配却汉,可以查詢地址中包括字符串 “Glasgow” 的所有業(yè)主斑举。查詢語句如下:
SELECT ownerNo, fName, lName, address, telNo
FROM PrivateOwner
WHERE address LIKE '%Glasgow%';
注意:一些 RDBMS,如 Microsoft Office Access病涨,使用通配符 * 和 富玷? 代替 % 和 _。
例 6.10 空查找條件(IS NULL / IS NOT NULL):
列出查看過房產(chǎn)編號(hào)為 PG4 的房產(chǎn)但沒有留下評(píng)論的客戶的情況既穆。
對(duì)于沒有評(píng)論的情況你可能會(huì)認(rèn)為用下面這個(gè)表達(dá)式查詢:
(propertyNo = 'PG4' AND comment = '')
然而赎懦,這種查詢方式是無效的』霉ぃ空評(píng)論可以被認(rèn)為是一個(gè)未知值励两,不能測試它是否等于另一個(gè)字符串。若用上面的復(fù)合條件囊颅,得到的結(jié)果將是一個(gè)空表当悔。相反,用特定保留字 IS NULL 可顯式地測試空值:
SELECT clientNo, viewDate
FROM Viewing
WHERE propertyNo = 'PG4' AND comment IS NOT NULL;
查詢結(jié)果排序(ORDER BY 子句)
一般來說踢代,SQL 查詢結(jié)果中的各行不會(huì)自動(dòng)以某種順序來顯示(雖然有一些 DBMS基于默認(rèn)排序盲憎,例如基于主關(guān)鍵字)。這時(shí)胳挎,可以使用 ORDER BY 子句讓查詢結(jié)果按一定順序顯示饼疙。ORDER BY 子句包括所需排序的 列標(biāo)識(shí)符 的列表,用逗號(hào)隔開慕爬。列標(biāo)識(shí)符 可能是 列名字 或 列序號(hào)窑眯,列序號(hào)是指列在 SELECT 列表中的位置,“1” 標(biāo)識(shí)列表中第一個(gè)(最左邊)元素医窿,“2”表示列表中第二個(gè)元素磅甩,以此類推。當(dāng)被排序的列是表達(dá)式并且沒有使用過 AS 子句賦予列將來可能引用的名字時(shí)姥卢,可以用列序號(hào)卷要。ORDER BY 子句允許導(dǎo)出的行在任一列或多個(gè)列上按升序(ASC)或降序(DESC)排列,而不管列是否出現(xiàn)在查詢結(jié)果中。然而一些實(shí)現(xiàn)版本要求 ORDER BY 子句中的元素必須出現(xiàn)在 SELECT 列表中却妨。不論是哪一種情況饵逐,ORDER BY 子句都只能是 SELECT 語句的最后一個(gè)子句括眠。
例 6.11 單列排序:
按工資降序的方式產(chǎn)生所有職工的工資列表彪标。
SELECT staffNo, fName, lName, salary
FROM Staff
ORDER BY salary DESC;
本例類似于例 6.2,不同在于此處查詢結(jié)果按工資降序排列掷豺。這主要由 SELECT 語句最后的 ORDER BY 子句完成捞烟,指定列 salary 用于排序,并使用 DESC 表示按降序排列当船。注意题画,還可以這樣表達(dá) ORDER BY 子句:ORDER BY 4 DESC,4 表示 SELECT 列表中第 4 列的名字德频,即 salary苍息。
ORDER BY 子句可能包括多個(gè)元素,主排序關(guān)鍵字 決定查詢結(jié)果總體的排序壹置。例 6.11 中竞思,主排序關(guān)鍵字是 salary。如果主關(guān)鍵字是唯一的钞护,那么就沒有必要引入第二個(gè)關(guān)鍵字來控制順序盖喷。然而,如果主排序關(guān)鍵字的值不是唯一的难咕,查詢結(jié)果中就會(huì)有多個(gè)行對(duì)應(yīng)主排序關(guān)鍵字的同一個(gè)值课梳,這種情況下,可以增加一個(gè)排序關(guān)鍵字來控制主排序關(guān)鍵字相同的那些行的順序余佃。ORDER BY 子句中第二個(gè)元素也成為 次排序關(guān)鍵字暮刃。
例 6.12 多列排序:
產(chǎn)生按類型排序的一個(gè)房產(chǎn)簡表。
SELECT propertyNo, type, rooms, rent
FROM PopertyForRent
ORDER BY type;
這時(shí)如果不指定次排序關(guān)鍵字爆土,系統(tǒng)可以任何順序?qū)π羞M(jìn)行排序沾歪。此時(shí),可以再以租金為序組織房產(chǎn)雾消,需指定次要順序:
SELECT propertyNo, type, rooms, rent
FROM PropertyForRent
ORDER BY type, rent DESC;
現(xiàn)在灾搏,查詢結(jié)果首先以房產(chǎn)類型升序排列(默認(rèn)值為ASC),對(duì)于相同房產(chǎn)類型立润,以 rent 降序排列狂窑。
ISO標(biāo)準(zhǔn)指出,若 ORDER BY 子句中用于排序的列或表達(dá)式取空值桑腮,則即可以認(rèn)為空值小于所有的非空值泉哈,也可以認(rèn)為空值大于所有的非空值。這個(gè)選擇權(quán)留給了 DBMS 的實(shí)現(xiàn)者。
使用 SQL 聚集函數(shù)
為了便于獲取數(shù)據(jù)庫中的行和列丛晦,我們通常希望對(duì)數(shù)據(jù)進(jìn)行匯總或聚集操作奕纫,類似于報(bào)表底部的合計(jì)。ISO 標(biāo)準(zhǔn)定義了五個(gè)聚集函數(shù):
- COUNT:返回指定列中數(shù)據(jù)的個(gè)數(shù)烫沙。
- SUM:返回指定列中數(shù)據(jù)的總和匹层。
- AVG:返回指定列中數(shù)據(jù)的平均值。
- MIN:返回指定列中數(shù)據(jù)的最小值锌蓄。
- MAX:返回指定列中數(shù)據(jù)的最大值升筏。
這些函數(shù)只對(duì)表中的單個(gè)列進(jìn)行操作,返回一個(gè)值瘸爽。COUNT您访、MIN 和 MAX 可以用于數(shù)值和非數(shù)值字段,而 SUM 和 AVG 只能用于數(shù)值字段剪决,除了 COUNT(*)外灵汪,每一個(gè)函數(shù)首先要去掉空值,然后計(jì)算其非空值柑潦。COUNT(*)是 COUNT 的特殊用法享言,計(jì)算表中所有行的數(shù)目,而不管是否有空值或重復(fù)出現(xiàn)妒茬。
若需要在應(yīng)用函數(shù)之前消除重復(fù)担锤,則必須在函數(shù)中的列名前使用關(guān)鍵字 DISTINCT。如果不需要去掉重復(fù)乍钻,ISO 標(biāo)準(zhǔn)允許使用關(guān)鍵字 ALL肛循,雖然指定這個(gè)關(guān)鍵字事實(shí)上跟不指定沒什么區(qū)別。DISTINCT 對(duì) MIN 和 MAX 函數(shù)沒有任何作用银择,而對(duì) SUM 和 AVG 函數(shù)有效多糠。所以計(jì)算時(shí)必須考慮重復(fù)項(xiàng)是否包含在計(jì)算中。另外浩考,查詢中 DISTINCT 只能指定一次夹孔。
注意聚集函數(shù)只能用于 SELECT 列表和 HAVING 子句中,用在其他地方都是不正確的析孽。如果 SELECT 列表包括聚集函數(shù)搭伤,卻沒有使用 GROUP BY 子句分組,那么 SELECT 列表的任何項(xiàng)都不能引用列袜瞬,除了作為聚集函數(shù)的參數(shù)怜俐。例如,下面的查詢是非法的:
SELECT staffNo, COUNT(salary)
FROM Staff;
因?yàn)椴樵冎袥]有 GROUP BY 子句邓尤,且 SELECT 列表中出現(xiàn)了列 staffNo拍鲤,它并不是聚集函數(shù)的參數(shù)贴谎。
例 6.13 COUNT(*)的使用:
月租金超過 350 英鎊的房產(chǎn)有多少處?
SELECT COUNT(*) AS myCount
FROM PropertyForRent
WHERE rent > 350;
用 WHERE 子句限制查詢每月租金超過 350 英鎊的房產(chǎn)季稳,滿足該條件的房產(chǎn)總數(shù)通過聚集函數(shù) COUNT 得出擅这。
例 6.14 COUNT(DISTINCT)的使用:
2013 年 5 月有多少處不同的房產(chǎn)被查看過?
SELECT COUNT(DISTINCT propertyNo) AS myCount
FROM Viewing
WHERE viewDate BETWEEN '1-May-13' AND '31-May-13';
用 WHERE 子句將查看房產(chǎn)的時(shí)間限定在 2013 年 5 月景鼠,滿足條件的總數(shù)通過聚集函數(shù) COUNT 得出仲翎。這樣相同的房產(chǎn)被重復(fù)計(jì)數(shù),必須用 DISTINCT 關(guān)鍵字去掉重復(fù)莲蜘。
例 6.15 COUNT 和 SUM 的使用:
找出經(jīng)理的總?cè)藬?shù)谭确,并計(jì)算他們的工資總和帘营。
SELECT COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
WHERE position = 'Manager';
用 WHERE 子句限制對(duì)經(jīng)理進(jìn)行查詢票渠。經(jīng)理人數(shù)和工資總和分別通過將 COUNT 和 SUM 函數(shù)應(yīng)用于受限集合而得出。
例 6.16 MIN芬迄、MAX 和 AVG 的使用:
找出所有員工工資的最小问顷、最大和平均值。
SELECT MIN(salary) AS myMin, MAX(salary) AS myMax, AVG(salary) AS myAvg
FROM Staff;
這個(gè)例子是查找所有員工的工資情況禀梳,所以不需要 WHERE 子句杜窄。對(duì)于列 salary,利用 MIN算途、MAX 和 AVG 函數(shù)求出所需要的值塞耕。
查詢結(jié)果分組(GROUP BY 子句)
上面匯總查詢的結(jié)果相當(dāng)于報(bào)表底部的合計(jì)值。報(bào)表中將查詢結(jié)果用一個(gè)匯總行表示嘴瓤,而縮減了細(xì)節(jié)數(shù)據(jù)扫外。通常報(bào)表中也需要有部分和,可用 GROUP BY 子句實(shí)現(xiàn)這種功能廓脆。包括 GROUP BY 子句的查詢稱為 分組查詢筛谚,按 SELECT 列表中的列進(jìn)行分組,每一組產(chǎn)生一個(gè)綜合查詢結(jié)果停忿。GROUP BY 子句的列又稱為 組列名驾讲。ISO 標(biāo)準(zhǔn)要求 SELECT 子句和 GROUP BY 子句緊密結(jié)合。當(dāng)使用 GROUP BY 時(shí)席赂,SELECT 列表中的項(xiàng)必須每組都有單一值吮铭。SELECT 子句僅可包括以下內(nèi)容:
- 列名
- 聚集函數(shù)
- 常量
- 組合上述各項(xiàng)的表達(dá)式
SELECT 子句中的所有列除非用在聚集函數(shù)中,否則必須在 GROUP BY 子句中出現(xiàn)颅停。反之谓晌,GROUP BY 子句中出現(xiàn)的列不一定出現(xiàn)在 SELECT 列表中。當(dāng) WHERE 子句和 GROUP BY 子句同時(shí)使用時(shí)便监,必須首先使用 WHERE 子句扎谎,分組由滿足 WHERE 子句查詢條件的那些行產(chǎn)生碳想。
ISO 標(biāo)準(zhǔn)規(guī)定應(yīng)用 GROUP BY 子句時(shí),兩個(gè)空值被認(rèn)為是相等的毁靶。即如果兩行在同一分組列上都為空值胧奔,并且在不含的分組列上值相等,則這兩行被合并到同一組中预吆。
例 6.17 GROUP BY 的使用:
找出工作在每一個(gè)分公司的員工人數(shù)和他們的工資總和龙填。
SELECT branchNo, COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
GROUP BY branchNo
ORDER BY branchNo;
GROUP BY 列表中不必包括列名 staffNo 和 salary,因?yàn)槠鋬H出現(xiàn)在 SELECT 列表的聚集函數(shù)中拐叉。另一方面岩遗,branchNo 沒有出現(xiàn)在聚集函數(shù)中,所以必須出現(xiàn)在 GROUP BY 列表中凤瘦。
理論上宿礁,SQL 按下列步驟完成查詢:
- SQL 根據(jù)分公司編號(hào)將員工分成不同的組。每一組中蔬芥,所有員工有相同的分公司編號(hào)梆靖。
- 每一組中,SQL 計(jì)算員工的人數(shù)笔诵,并計(jì)算出 salary 列的匯總以便得到員工薪水的總和返吻。SQL 在查詢結(jié)果中為每一組生成一個(gè)單獨(dú)的匯總行。
- 最后乎婿,查詢結(jié)果按分公司編號(hào) branchNo 的升序排列测僵。
SQL 標(biāo)準(zhǔn)允許 SELECT 列表包括某些嵌套查詢,因而可用下面的語句表達(dá)上面的查詢:
SELECT branchNo, (SELECT COUNT(staffNo) AS myCount
FROM Staff s
WHERE s.branchNo = b.branchNo),
(SELECT SUM(salary) AS mySum
FROM Staff s
WHERE s.branchNo = b.branchNo)
FROM Branch b
ORDER BY branchNo;
該例中谢翎,將為 Branch 列中的每個(gè)分公司產(chǎn)生兩個(gè)聚集函數(shù)值捍靠,某些情況下聚集值可能為零。
分組約束(HAVING 子句)
HAVING 子句的設(shè)計(jì)意圖食欲 GROUP BY 子句一起使用岳服,來限定哪些 分組 將出現(xiàn)在最終查詢結(jié)果中剂公。雖然它與 WHERE 子句語法類似,但用途不同吊宋。WHERE 子句將單個(gè)行 “過濾” 到查詢結(jié)果中纲辽,而 HAVING 子句則將 分組 “過濾” 到查詢結(jié)果表中。ISO 標(biāo)準(zhǔn)要求 HAVING 子句使用的列名必須出現(xiàn)在 GROUP BY 子句列表中璃搜,或包括在聚集函數(shù)中拖吼。實(shí)際中,HAVING 子句的條件運(yùn)算至少包括一個(gè)聚集函數(shù)这吻,否則的話可把查詢條件移到 WHERE 子句中來過濾單個(gè)行(記住聚集函數(shù)不能用在 WHERE 子句中)吊档。
HAVING 子句并不是 SQL 的必要部分——任何使用 HAVING 子句的查詢都可用不帶 HAVING 子句的語句重寫。
例 6.18 HAVING 的使用:
對(duì)于員工人數(shù)多于一人的分公司計(jì)算出每一個(gè)分公司的員工人數(shù)和他們的工資總和唾糯。
SELECT branchNo, COUNT(staffNo) AS myCount, SUM(salary) AS mySum
FROM Staff
GROUP BY branchNo
HAVING COUNT(staffNo) > 1
ORDER BY branchNo;
此例類似于前面講過的附加約束的例子怠硼,只找出員工人數(shù)超過一人的分組鬼贱,可用 HAVING 子句對(duì)分組進(jìn)行約束。
子查詢
這里討論將 SELECT 語句完全嵌套到另一個(gè) SELECT 語句中的用法香璃。內(nèi)部 SELECT 語句(子查詢)的結(jié)果用在外部語句中以決定最后的查詢結(jié)果这难。子查詢可以被使用在外部 SELECT 語句的 WHERE 和 HAVING 子句中,稱為 子查詢 或 嵌套查詢葡秒。子查詢也可以出現(xiàn)在 INSERT姻乓、UPDATE 和 DELETE 語句中。子查詢有三種類型:
- 標(biāo)量子查詢:返回單個(gè)列和單個(gè)行眯牧,即單個(gè)值蹋岩。原則上,標(biāo)量子查詢可用于任何需要單個(gè)值的地方学少。
- 行子查詢:返回多個(gè)列剪个,但只有單個(gè)行。行子查詢可用于任何需要行值構(gòu)造器的時(shí)候旱易,如在謂詞中禁偎。
- 表子查詢:返回多個(gè)行腿堤,每行有一個(gè)或多個(gè)列阀坏。表子查詢用于需要一個(gè)表的情況。例如笆檀,作為謂詞 IN 的操作數(shù)忌堂。
例 6.19 用于相等判斷的子查詢:
列出在位于 “163 Main St” 的分公司中工作的員工的情況。
SELECT staffNo, fName, lName,position
FROM Staff
WHERE branchNo = (SELECT branchNo
FROM Branch
WHERE street = '163 Main St');
內(nèi)部 SELECT 語句(SELECT branchNo FROM Branch...)找出位于 “163 Main St” 的分公司的編號(hào)(如果只有一個(gè)分公司編號(hào)酗洒,便是標(biāo)量查詢)士修。外部 SELECT 語句找出工作在此分公司的所有員工的情況。換句話說樱衷,內(nèi)部查詢返回一個(gè)與 “163 Main St” 對(duì)應(yīng)的值 “B003”棋嘲,外部查詢語句變?yōu)椋?/p>
SELECT staffNo, fName, lName, position
FROM Staff
WHERE branchNo = 'B003';
可以認(rèn)為子查詢產(chǎn)生一個(gè)臨時(shí)表,便與外部語句訪問和利用矩桂。在 WHERE 子句和 HAVING 子句中沸移,子查詢可以緊鄰著關(guān)系運(yùn)算符。子查詢本身通常包括在圓括號(hào)中侄榴。
例 6.20 用于聚集函數(shù)的子查詢:
列出個(gè)人工資高于平均工資的所有員工雹锣,并求出多余平均數(shù)的值。
SELECT staffNo, fName, lName, position, salary - (SELECT AVG(salary) FROM Staff) AS salDiff
FROM Staff
WHERE salary > (SELECT AVG(salary) FROM Staff);
首先注意癞蚕,不能寫 “WHERE salary > AVG(salary)”蕊爵,因?yàn)榫奂瘮?shù)不能用于 WHERE 子句中。相反桦山,先用子查詢求出平均工資攒射,然后使用外部 SELECT 語句找出那些工資高于平均數(shù)的員工醋旦。換句話說,子查詢返回的是平均工資 17000 英鎊会放。注意浑度,在 SELECT 列表中使用了標(biāo)量子查詢,得以表示與平均工資的差鸦概。外查詢可簡寫如下:
SELECT staffNo, fName, lName, position, salary - 17000 AS salDiff
FROM Staff
WHERE salary > 17000;
子查詢應(yīng)遵循如下規(guī)則:
- ORDER BY 子句不能用于子查詢(雖然可用在最外面的 SELECT 語句中)箩张。
- 子查詢 SELECT 列表必須由單個(gè)列名或表達(dá)式組成,除非子查詢使用了關(guān)鍵詞 EXISTS窗市。
- 默認(rèn)的情況下先慷,子查詢中列名取自子查詢的 FROM 子句中給出的表,也可通過限定列名的辦法指定取自外查詢的 FROM 子句中的表咨察。
- 當(dāng)子查詢時(shí)比較表達(dá)式中的一個(gè)操作時(shí)论熙,子查詢必須出現(xiàn)在表達(dá)式的右面匆赃。
例 6.21 嵌套子查詢朗儒,IN 的使用:
列出正由位于 “163 Main St” 的分公司的員工經(jīng)營的房產(chǎn)。
SELECT propertyNo, street, city, postcode, type, rooms, rent
FROM PropertyForRent
WHERE staffNo IN (SELECT staffNo
FROM Staff
WHERE branchNo = (SELECT branchNo
FROM Branch
WHERE street = '163 Main St'));
最里面的查詢擂红,首先是查詢位于 “163 Main St” 的分公司的編號(hào)媒役,然后查詢工作在這個(gè)分公司的員工祝谚。這時(shí),可能會(huì)出現(xiàn)多個(gè)行酣衷,所以最外面的查詢不能用等號(hào)(=)交惯,而是用 IN 關(guān)鍵字。最外層查詢出中間層得到的員工管理的房產(chǎn)的情況穿仪。
ANY 和 ALL
關(guān)鍵字 ANY 和 ALL 用于產(chǎn)生單個(gè)列的子查詢席爽。若子查詢前綴關(guān)鍵字 ALL,那么僅當(dāng)子查詢產(chǎn)生的所有值都滿足條件時(shí)啊片,條件才為真只锻。若子查詢前綴關(guān)鍵字 ANY,那么子查詢產(chǎn)生的任何一個(gè)值(一個(gè)或多個(gè))滿足條件時(shí)紫谷,條件就為真齐饮。如果子查詢是空值,ALL條件返回真值碴里,ANY 條件返回假值沈矿。ISO 標(biāo)準(zhǔn)允許用限定詞 SOME 代替 ANY。
例 6.22 ANY / SOME 的使用:
列出工資高于分公司 B003 中至少一位員工的工資的所有員工咬腋。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary > SOME(SELECT salary
FROM Staff
WHERE branchNo = 'B003');
這個(gè)查詢中羹膳,首先用子查詢找出工作在分公司 B003 的員工的最低工資,然后根竿,外查詢找出工資高于這個(gè)數(shù)值的所有員工陵像。另一種方法是使用關(guān)鍵字 SOME / ANY就珠。內(nèi)查詢產(chǎn)生集合,外查詢找出工資高于集合中任一個(gè)數(shù)值的員工醒颖。這種方法看起來比找出子查詢中最低工資更自然一些妻怎。
例 6.23 ALL 的使用:
列出工資高于分公司 B003 中任何員工的工資的所有員工。
SELECT staffNo, fName, lName, position, salary
FROM Staff
WHERE salary > ALL(SELECT salary
FROM Staff
WHERE branchNo = 'B003');
這個(gè)例子和上一個(gè)例子非常相似泞歉。首先用子查詢找出分公司 B003 中原工工資的最大數(shù)值逼侦,然后,外查詢找出工資高于這個(gè)數(shù)值的所有員工腰耙。在這個(gè)例子中使用了關(guān)鍵字 ALL榛丢。
多表查詢
以上例子的一個(gè)最大問題是限定查詢結(jié)果中出現(xiàn)的所有列必須來自同一個(gè)表。很多情況下挺庞,一個(gè)表不夠晰赞。要把來自多個(gè)表的列組合到結(jié)果表時(shí),就需要用到 連接 操作选侨。SQL 連接操作通過配對(duì)相關(guān)行來合并兩個(gè)表中的信息掖鱼。而構(gòu)成連接表的配對(duì)行是指這兩行在兩個(gè)表的匹配列上具有相同值。
要從多個(gè)表中得出查詢結(jié)果援制,可用子查詢戏挡,也可用連接操作。如果最終結(jié)果表包括了多個(gè)表中的列隘谣,則必須用連接操作增拥。連接操作中,F(xiàn)ROM 子句列出多個(gè)表明寻歧,之間用逗號(hào)分開,通常還要用 WHERE 子句來指明連接列秩仆。在 FROM 子句中也可用 別名 代替表名码泛,它們之間用空格分開。別名可在列名有歧義的時(shí)候用來指定列名澄耍。別名也可以用來作為表名的簡寫噪珊。如果定義了別名,則可在任何地方用它代替表名齐莲。
例 6.24 簡單連接:
列出查看過房產(chǎn)的所有客戶的姓名及其所提的意見痢站。
SELECT c.clientNo, fName, lName, propertyNo, comment
FROM Client c, Viewing v
WHERE c.clientNo = v.clientNo;
要顯示來自表 Client 和表 Viewing 的細(xì)節(jié)信息需要用到連接。SELECT 子句中列出了需要顯示的列选酗。注意阵难,必須在 SELECT 列表中限定員工編號(hào) clientNo:由于 clientNo 可以來自兩個(gè)表中任意一個(gè),所以必須指定來自哪一個(gè)(選擇表 Viewing中的員工標(biāo)號(hào) clientNo 也可以)芒填,在列名前綴以表名(或別名)即可實(shí)現(xiàn)這種限定呜叫。此例中空繁,用 c 作為表 Client 的別名。
為得到所需的行朱庆,用查詢條件(c.clientNo = v.clientNo)得到了兩個(gè)表中在列 clientNo 上有相同值的那些行盛泡。該列也稱為兩個(gè)表的 匹配列。這類似于前面關(guān)系代數(shù)部分踢桃的相等連接(Equijoin)操作娱颊。
最普通的多表查詢包括一對(duì)多(1:*)(或父 / 子)聯(lián)系的兩個(gè)表傲诵。前面涉及 client 和 viewing 關(guān)系的查詢就是這樣的例子。每一次看房(子)對(duì)應(yīng)著一個(gè)客戶(父)箱硕,每個(gè)客戶(父)可能多次去看房(子)掰吕,產(chǎn)生的查詢結(jié)果中的行對(duì)是父 / 子行的結(jié)合。主關(guān)鍵字所在的表是父表颅痊,外部關(guān)鍵字所在的表是子表。要在 SQL 查詢中使用父 / 子聯(lián)系,則需要指定比較主關(guān)鍵字和外部關(guān)鍵字的查找條件。
SQL 標(biāo)準(zhǔn)提供了下列可選擇的方式來指定連接:
FROM Client c JOIN Viewing v ON c.clientNo = v.clientNo
FROM Client JOIN Viewing USING clientNo
FROM Client NATURAL JOIN Viewing
在每一種情況中,F(xiàn)ROM 子句都替代原來的 FROM 和 WHERE 子句。只是第一種方式產(chǎn)生的表中有兩個(gè) clientNo 列,而其他兩種方式產(chǎn)生的表中只有一個(gè) clientNo 列膜赃。
例 6.25 排序連接結(jié)果:
對(duì)每一個(gè)分公司,列出管理房產(chǎn)的員工的姓名、編號(hào)及其正在管理的房產(chǎn)产舞。
SELECT s.branchNo, s.staffNo, fName, lName, propertyNo
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
ORDER BY s.branchNo, s.staffNo, propertyNo;
這里為了使查詢結(jié)果有更好的可讀性哈蝇,按分公司編號(hào)作為主排序關(guān)鍵字样勃,員工編號(hào)和房產(chǎn)編號(hào)作為次關(guān)鍵字進(jìn)行排序幌陕。
例 6.26 三表連接:
對(duì)每一個(gè)分公司列出管理房產(chǎn)的員工姓名心例、編號(hào)译株,以及分公司所在的城市和員工管理的房產(chǎn)。
SELECT b.branchNo, b.city, s.staffNo, fName, lName, propertyNo
FROM Branch b, Staff s, PropertyForRent p
WHERE b.branchNo = s.branchNo AND s.staffNo = p.staffNo
ORDER BY b.branchNo, s.staffNo, propertyNo;
查詢結(jié)果所需的列來自三個(gè)表:Branch蚤氏、Staff 和 PropertyForRent于游,所以必須用連接操作。用相等條件(b.branchNo = s.branchNo)連接表 Branch 和表 Staff葵袭,將每個(gè)分公司和在那里工作的員工連接起來,用相等條件(s.staffNo = p.staffNo)連接表 Staff 和表PropertyForRent禽额,將每位員工和其管理的房產(chǎn)連接起來锯厢。
注意皮官,SQL 標(biāo)準(zhǔn)為 FROM 和 WHERE 子句提供可選的表示法,例如:
FROM (Branch b JOIN Staff s USING branchNo) AS bs
JOIN PropertyForRent p USING staffNo
例 6.27 按多個(gè)列分組:
找出每一位員工管理的房產(chǎn)的數(shù)量实辑,以及該員工所在的分公司編號(hào)捺氢。
SELECT s.branchNo, s.staffNo, COUNT(*) AS myCount
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
GROUP BY s.branchNo, s.staffNo
ORDER BY s.branchNo, s.staffNo;
為列出所需的數(shù)據(jù),首先找到員工管理的房產(chǎn)剪撬。在 FROM / WHERE 子句中摄乒,用 staffNo 列連接表 Staff 和 PropertyForRent。下一步婿奔,用 GROUP BY 子句形成按分公司編號(hào)和員工編號(hào)的分組缺狠。最后,用 ORDER BY 子句排序萍摊。
連接運(yùn)算的計(jì)算過程
連接操作其實(shí)是更一般的兩表合并挤茄,所謂 笛卡爾積 的子集。兩個(gè)表的笛卡爾積是包括兩個(gè)表中所有可能的行對(duì)的一個(gè)新表冰木。新表的列是第一個(gè)表的所有列后加上第二個(gè)表的所有列穷劈。如果兩個(gè)表的查詢不使用 WHERE 子句,那么 SQL產(chǎn)生的查詢結(jié)果就是兩個(gè)表的笛卡爾積踊沸。事實(shí)上歇终,ISO 標(biāo)準(zhǔn)為笛卡爾積提供了特殊的 SELECT 語句格式:
SELECT [DISTINCT | ALL] {* | columnList}
FROM TableName1 CROSS JOIN TableName2
從概念上看,使用連接的 SELECT 語句查詢過程如下:
- 形成 FROM 子句中指定的笛卡爾積逼龟。
- 如果存在 WHERE 子句评凝,對(duì)乘積表的每一行運(yùn)用條件查找,保留那些滿足條件的行腺律,用關(guān)系代數(shù)術(shù)語來說奕短,這個(gè)操作即對(duì)笛卡爾積的 限制。
- 對(duì)于每個(gè)剩下的行匀钧,確定 SELECT 列表中每一項(xiàng)的值翎碑,并形成查詢結(jié)果中的一行。
- 如果指定了 SELECT DISTINCT之斯,則消除結(jié)果中重復(fù)的行日杈。以關(guān)系代數(shù)看,第 3 步和第 4 步相當(dāng)于把第二步得到的限制在 SELECT 列表列上進(jìn)行 投影佑刷。
- 如果存在 ORDER BY 子句莉擒,則根據(jù)要求對(duì)查詢結(jié)果進(jìn)行排序。
外連接
連接操作通過配對(duì)相關(guān)的行來組合兩個(gè)表中的數(shù)據(jù)瘫絮,即找到在兩個(gè)表的匹配列上具有相同值的行啰劲。如果表中某一行不匹配另一表的任何行,那么這行將從結(jié)果表中刪除檀何。這就是上面所討論的連接問題。ISO 標(biāo)準(zhǔn)提供的另一類連接操作稱為外連接。外連接保留不滿足連接條件的行频鉴。為了更好地理解外連接操作栓辜, 請(qǐng)看下面兩個(gè)簡化的表 Branch 和 PropertyForRent,分別稱為 Branch1 和 PropertyForRent1垛孔。
Branch1:
branchNo | bCity |
---|---|
B003 | Glasgow |
B004 | Bristol |
B002 | London |
PropertyForRent1:
propertyNo | pCity |
---|---|
PA14 | Aberdeen |
PL94 | London |
PG4 | Glasgow |
兩個(gè)表的內(nèi)連接如下:
SELECT b.*, p.*
FROM Branch1 b, PropertyForRent p
WHERE b.bCity = p.pCity;
產(chǎn)生的結(jié)果如下表所示:
branchNo | bCity | propertyNo | pCity |
---|---|---|---|
B003 | Glasgow | PG4 | Glasgow |
B002 | London | PL94 | London |
結(jié)果表輸出兩個(gè)表中相同城市的所有行藕甩。特別注意,沒有行與 Bristol 的分公司匹配周荐,也沒有行與 Aberdeen 的房產(chǎn)匹配狭莱。如果希望不匹配的行也出現(xiàn)在結(jié)果表中,就需要用到外連接概作。外連接有三種類型:左外連接腋妙、右外連接 和 全外連接。
例 6.28 左外連接:
列出所有分公司及與其處于同一城市的房產(chǎn)讯榕。
這兩個(gè)表的左外連接:
SELECT b.*, p.*
FROM Branch1 b LEFT JOIN PropertyForRent1 p ON b.bCity = p.pCity;
本例中左外連接不僅包括了那些城市列值相同的行骤素,還包括了第一個(gè)(左邊)表中與第二個(gè)(右邊)表無匹配行的那些行。對(duì)這些行愚屁,來自第二個(gè)表的列上填 NULL济竹。
例 6.29 右外連接:
列出所有房產(chǎn)以及與其同處一城的分公司。
這兩個(gè)表的右外連接:
SELECT b.*, p.*
FROM Branch1 b RIGHT JOIN PropertyForRent1 p ON b.bCity = p.pCity;
右外連接不僅包括了相同城市列值得行霎槐,還包括了第二個(gè)(右邊)表中與第一個(gè)(左邊)表無匹配行的那些行送浊。對(duì)這些行,來自第一個(gè)表的列上填寫 NULL丘跌。
例 6.30 全外連接:
列出處于同一城市的分公司和房產(chǎn)袭景,包括不匹配的分公司和房產(chǎn)。
這兩個(gè)表的全外連接:
SELECT b.*, p.*
FROM Branch1 b FULL JOIN PropertyForRent1 p ON b.bCity = p.pCity;
全外連接不僅包括具有相同城市列值的行碍岔,還包括兩個(gè)表中不匹配的行浴讯。這些不匹配的列上填寫 NULL。
EXISTS 和 NOT EXISTS
關(guān)鍵字 EXISTS 和 NOT EXISTS 僅用于子查詢中蔼啦,返回結(jié)果為真 / 假榆纽。EXISTS 為真當(dāng)且僅當(dāng)子查詢返回的結(jié)果表至少存在一行,當(dāng)子查詢返回的結(jié)果表為空時(shí)則為假捏肢。NOT EXISTS 正相反奈籽。由于 EXISTS 和 NOT EXISTS 僅檢查子查詢結(jié)果中是否存在行,所以子查詢可查詢?nèi)我鈹?shù)目的列鸵赫。換句話說衣屏,子查詢通常用下列形式表示:
(SELECT * FROM ...)
例 6.31 使用 EXISTS 查詢:
找出工作在倫敦分公司的所有員工。
SELECT staffNo, fName, lName, position
FROM Staff s
WHERE EXISTS (SELECT *
FROM Branch b
WHERE s.branchNo = b.branchNo AND city = 'London');
查詢語句可改寫為 “找出所有這樣的員工辩棒,其分公司編號(hào)為 branchNo狼忱,對(duì)應(yīng)表 Branch 中的一行膨疏,并且該分公司所在的城市為倫敦”,該測試就是測試是否存在這樣一行钻弄。如果存在佃却,子查詢?yōu)檎妗?/p>
注意,查找條件的第一部分 s.branchNo = b.branchNo 非常必要窘俺,可以確保員工屬于指定的分公司饲帅。如果漏掉這一部分,則將會(huì)列出所有的員工瘤泪,因?yàn)樽硬樵儯⊿ELECT * FROM Branch WHERE city = 'London')總為真灶泵,該子查詢將簡化為:
SELECT staffNo, fName, lName, position FROM Staff WHERE true;
等價(jià)于
SELECT staffNo, fName, lName, position FROM Staff;
也可以用連接重寫這個(gè)查詢:
SELECT staffNo, fName, lName, position
FROM Staff s, Branch b
WHERE s.branchNo = b.branchNo AND city = 'London';
合并結(jié)果表(UNION、INTERSECT 和 EXCEPT)
SQL 中对途,可用標(biāo)準(zhǔn)的并赦邻、交和差集合操作將多個(gè)查詢結(jié)果表合并成一個(gè)查詢結(jié)果表:
- A 和 B 兩個(gè)表的 并 操作是一個(gè)包括兩個(gè)表中所有行的表。
- A 和 B 兩個(gè)表的 交 操作是一個(gè)包括兩個(gè)表中共有行的表掀宋。
- A 和 B 兩個(gè)表的 差 操作是一個(gè)包括那些在 A 中而不在 B 中的行的表深纲。
用于集合操作的表有一些限制,最重要的一點(diǎn)是兩個(gè)表具有 并相容性劲妙,也就是說要具有相同的結(jié)構(gòu)湃鹊。即兩個(gè)表必須包含相同數(shù)目的列,且對(duì)應(yīng)的列具有相同的數(shù)據(jù)類型和長度镣奋。用戶必須確保對(duì)應(yīng)列的數(shù)值來自相同的 域币呵。例如,將員工年齡的列與房產(chǎn)中房間數(shù)量進(jìn)行組合是不明智的侨颈,盡管這兩列有相同的數(shù)據(jù)類型余赢,例如 SMALLINT。
ISO 標(biāo)準(zhǔn)中的三個(gè)集合運(yùn)算符分別是 UNION哈垢、INTERSECT 和 EXCEPT妻柒,集合操作子句格式如下:
operator [ALL][CORRESPONDING [BY {column1 [,...]}]]
如果指定 CORRESPONDING BY,則集合操作就在給定的列上執(zhí)行耘分。若指定 ALL 則查詢包括一切重復(fù)的行举塔。一些 SQL 的實(shí)現(xiàn)版本并不支持 INTERSECT 和 EXCEPT,還有一些實(shí)現(xiàn)用 MINUS 取代了 EXCEPT求泰。
例 6.32 UNION 的使用:
建立有分公司或有房產(chǎn)的所有城市的列表央渣。
(SELECT city
FROM Branch
WHERE city IS NOT NULL)
UNION
(SELECT city
FROM PropertyForRent
WHERE city IS NOT NULL);
或
(SELECT *
FROM Branch
WHERE city IS NOT NULL)
UNION CORRESPONDING BY city
(SELECT *
FROM PropertyForRent
WHERE city IS NOT NULL);
此例從第一個(gè)查詢中產(chǎn)生一個(gè)結(jié)果表,也從第二個(gè)查詢中產(chǎn)生一個(gè)結(jié)果表渴频,然后將兩個(gè)表合并為一個(gè)由兩個(gè)表中除去所有重復(fù)行組成的表芽丹。
例 6.33 INTERSECT 的使用:
建立既有分公司又有房產(chǎn)的所有城市的列表。
(SELECT city
FROM Branch
INTERSECT
(SELECT city
FROM PropertyForRent);
或
(SELECT *
FROM Branch
INTERSECT CORRESPONDING BY city
(SELECT *
FROM PropertyForRent);
此例從第一個(gè)查詢中產(chǎn)生一個(gè)結(jié)果表卜朗,也從第二個(gè)查詢中產(chǎn)生一個(gè)結(jié)果表拔第,然后將兩個(gè)表合并為一個(gè)由兩個(gè)表所有共有行組成的表咕村。
可以不用 INTERSECT 運(yùn)算符重寫這個(gè)查詢,例如:
SELECT DISTINCT b.city
FROM Branch b, PropertyForRent p
WHERE b.city = p.city;
或
SELECT DISTINCT city
FROM Branch b
WHERE EXISTS (SELECT *
FROM PropertyForRent p
WHERE b.city = p.city);
可用各種等價(jià)的形式書寫查詢是 SQL 語言的一大缺陷楼肪。
例 6.34 EXCEPT 的使用:
建立具有分公司但沒有房產(chǎn)的所有城市的列表培廓。
(SELECT city
FROM Branch)
EXCEPT
(SELECT city
FROM PropertyForRent);
或
(SELECT *
FROM Branch)
EXCEPT CORRESPONDING BY city
(SELECT *
FROM PropertyForRent);
查詢從第一個(gè)查詢中產(chǎn)生一個(gè)結(jié)果表,也從第二個(gè)查詢中產(chǎn)生一個(gè)結(jié)果表春叫,然后將兩個(gè)表合并為一個(gè)由在第一個(gè)表中而不在第二個(gè)表中的所有行組成的表。
可以不用 EXCEPT 運(yùn)算符重寫這個(gè)查詢泣港。例如:
SELECT DISTINCT city
FROM Branch
WHERE city NOT IN (SELECT city
FROM PropertyForRent);
或
SELECT DISTINCT city
FROM Branch b
WHERE NOT EXISTS (SELECT *
FROM PropertyForRent p
WHERE b.city = p.city);
數(shù)據(jù)庫更新
SQL 是一種完全的數(shù)據(jù)操作語言暂殖,可用于修改數(shù)據(jù)庫中的數(shù)據(jù),也可用于查詢數(shù)據(jù)庫当纱。修改數(shù)據(jù)庫的命令不像 SELECT 語句那樣復(fù)雜呛每。下面討論修改數(shù)據(jù)庫內(nèi)容的三種 SQL 語句。
- INSERT:向表中添加新的行坡氯。
- UPDATE:修改表中現(xiàn)有的行晨横。
- DELETE:刪除表中已有的行。
向數(shù)據(jù)庫中添加數(shù)據(jù)(INSERT)
這里有兩種 INSERT 語句形式箫柳,第一種是插入一個(gè)行的語句手形,格式如下:
INSERT INTO TableName [(columnList)]
VALUES (dataValueList)
TableName 是一個(gè)基表或是一個(gè)可更新的視圖,columnList 代表用逗號(hào)分開的一個(gè)或多個(gè)列名悯恍,該項(xiàng)是可選的库糠。如果省略 columnList,SQL 將嚴(yán)格按它們?cè)?CREATE TABLE 命令中得順序涮毫。如果給出 columnList瞬欧,則在 columnList 中未出現(xiàn)的列在建表時(shí)不能聲明為 NOT NULL,除非建該列時(shí)使用 DEFAULT 選項(xiàng)罢防。dataValueList 必須與 columnList 有如下匹配:
- 列表 columnList 與 dataValueList 中項(xiàng)的數(shù)目必須是相同的艘虎。
- 兩個(gè)列表中項(xiàng)的位置必須是直接對(duì)應(yīng)的,dataValueList 中的第一項(xiàng)對(duì)應(yīng)于 columnList 中的第一個(gè)項(xiàng)咒吐。dataValueList 中的第二項(xiàng)對(duì)應(yīng)于 columnList 中的第二項(xiàng)野建,等等。
- dataValueList 列表中每一項(xiàng)的數(shù)據(jù)類型必須和對(duì)應(yīng)列數(shù)據(jù)類型兼容渤滞。
例 6.35 INSERT ... VALUES:
向表 Staff 中插入包括所有列數(shù)據(jù)的一個(gè)新行贬墩。
INSERT INTO Staff
VALUES ('SG44', 'Alan', 'Brown', 'Assistant', 8100, 'M', DATE '1957-05-25', 8300, 'B003');
本例按照表創(chuàng)建時(shí)的列順序插入數(shù)據(jù),不必再指定列名列表妄呕。注意陶舞,Alan這樣的字符常量必須用單引號(hào)引起來。
例 6.36 默認(rèn)插入方式:
指定列 staffNo绪励、fName肿孵、lName唠粥、position、salary 和 branchNo停做,向表 Staff 中插入新行晤愧。
INSERT INTO Staff (staffNo, fName, lName, position, salary, branchNo)
VALUES ('SG44', 'Anne', 'Jones', 'Assistant', 8100, 'B003');
若插入數(shù)據(jù)到某些列上,則必須制定需插入數(shù)據(jù)的列名字蛉腌。列名的順序并不重要官份,但是按他們?cè)诒碇性境霈F(xiàn)的順序是比較常見的做法。也可以把 INSERT 語句寫成下面的形式:
INSERT INTO Staff
VALUES ('SG44', 'Anne', 'Jones', 'Assistant', NULL, NULL, 8100, 'B003');
本例中顯式的指定 sex 和 DOB 應(yīng)設(shè)置為 NULL烙丛。
第二種 INSERT 語句允許把一個(gè)或多個(gè)表中的多個(gè)行復(fù)制到另一個(gè)表舅巷,格式如下:
INSERT INTO TableName [(columnList)]
SELECT ...
插入單個(gè)行之前,TableName 和 columnList 必須預(yù)先定義好河咽,SELECT 子句可以是任何有效的語句钠右。插入給定表中的行就是來自于子查詢所生成的查詢結(jié)果表。用于第一種形式的 INSERT 語句的約束在這里也有效忘蟹。
例 6.37 INSERT ... SELECT:
假設(shè)表 StaffPropCount 中包含員工的名字及他所管理的房產(chǎn)的數(shù)目飒房。
StaffPropCount(staffNo, fName, lName, propCount)
用表 Staff 和表 PropertyForRent 中的數(shù)據(jù)產(chǎn)生表 StaffPropCount。
INSERT INTO StaffPropCount
(SELECT s.staffNo, fName, lName, COUNT(*)
FROM Staff s, PropertyForRent p
WHERE s.staffNo = p.staffNo
GROUP BY s.staffNo, fName, lName)
UNION
(SELECT staffNo, fName, lName, 0
FROM Staff s
WHERE NOT EXISTS (SELECT *
FROM PropertyForRent p
WHERE p.staffNo = s.staffNo));
因?yàn)樾枰?jì)算出員工管理的房產(chǎn)數(shù)目媚值,所以例子變得更加復(fù)雜狠毯。如果省略 UNION 的第二部分,則得到的是至少管理一處房產(chǎn)的員工的列表杂腰。換句話說垃你,就是把沒有管理過房產(chǎn)的員工排除掉了。所以喂很,為了包括沒有管理房產(chǎn)的員工惜颇,必須用 UNION 語句和第二個(gè) SELECT 語句增加這樣的員工,計(jì)數(shù)屬性設(shè)為 0少辣。
注意凌摄,一些 SQL 的實(shí)現(xiàn)版本不允許在 INSERT 子查詢中使用 UNION 運(yùn)算符。
修改數(shù)據(jù)庫中的數(shù)據(jù)(UPDATE)
UPDATE 語句允許改變給定表中已存在的行的內(nèi)容漓帅。命令格式如下:
UPDATE TableName
SET columnName1 = dataValue1 [, columnName2 = dataValue2 ...]
[WHERE searchCondition]
TableName 是基表或可更新視圖的名字锨亏。SET 子句指定需要更新的一個(gè)或多個(gè)列的名字。WHERE 子句是可選擇的忙干,如果省略器予,則對(duì)給定列的所有行進(jìn)行更新。如果給出 WHERE 子句捐迫,則僅對(duì)那些滿足 searchCondition 的行進(jìn)行更新乾翔,新的 dataValue 必須與對(duì)應(yīng)列中的數(shù)據(jù)類型兼容。
例 6.38 更新所有行:
把所有員工的工資提高 3%。
UPDATE Staff
SET salary = salary * 1.03;
這個(gè)更新是對(duì)表中所有行的反浓,故 WHERE 子句可省略萌丈。
例 6.39 更新指定的行:
把所有經(jīng)理的工資提高 5%。
UPDATE Staff
SET salary = salary * 1.05
WHERE position = 'Manager';
WHERE 子句找出所有經(jīng)理的行雷则,然后把更新 salary = salary * 1.05 應(yīng)用到特定的行辆雾。
例 6.40 更新多個(gè)列:
提升 David Ford(staffNo = 'SG14')為經(jīng)理,工資變?yōu)?18000 英鎊月劈。
UPDATE Staff
SET position = 'Manager', salary = 18000
WHERE staffNo = 'SG14';
刪除數(shù)據(jù)庫中的數(shù)據(jù)(DELETE)
DELETE語句允許從給定表中刪除行度迂。命令格式如下:
DELETE FROM TableName
[WHERE searchCondition];
與 INSERT 和 UPDATE 語句一樣,TableName 是基表或可更新視圖的名字艺栈。searchCondition 是可選的英岭,如果省略,則所有行都會(huì)被刪除湿右。注意,不是刪除表本身罚勾,而是刪除表的內(nèi)容毅人。若要既刪除內(nèi)容又刪除表定義,可用 DROP TABLE 語句尖殃。如果指定了 searchCondition丈莺,則僅刪除那些滿足條件的行。
例 6.41 刪除指定行:
刪除所有與房產(chǎn) PG4 有關(guān)的行送丰。
DELETE FROM Viewing
WHERE propertyNo = 'PG4';
WHERE 子句找出房產(chǎn)編號(hào)為 'PG4' 的所有行缔俄,刪除這些行。
例 6.42 刪除所有行:
DELETE FROM Viewing;
不給出 WHERE 子句器躏,將會(huì)刪去表中所有行俐载,只留下表定義,這樣做的目的是使稍后仍可插入數(shù)據(jù)登失。