Elementary SQL
Primitive Type
Primitive Type | Description |
---|---|
$char(n), character(n)$ | 固定長度的字符串 ,固定長度 為 n |
$varchar(n), character\ varying(n)$ | 可變長度的字符串 ,最大長度 為 n |
$int, integer$ | 整數類型 |
$smallint$ | 小整數類型 |
$numeric(p,d)$ | 定點數類型 :$numeric(3,1) \to 44.5$ |
$real, double\ precision$ | 浮點數 和 雙精度浮點數 |
$float(n)$ | 精度至少為n位的浮點數 |
空值 (null)
:任何數據類型
都可能包含空值
。空值
表達語義缺失的值
。對於使用
char
類型存放的字符串
,會自動執行末尾補空格
策略。但請註意,當我們使用
char
和varchar
進行相等性測試
時,行為
將是未定義的
,某些實現可能會自動為varchar在末尾補空格
。出於這點考慮,建議
永遠只使用varchar
Define a Database Pattern
General Form
CREATE TABLE r
(Attribute1 Domain1,
Attribute2 Domain2,
...,
Attributen Domainn,
<Integrity Constraint>,
...,
<Integrity Constraint>);
域 (Domain)
:用於指定與域相關聯的屬性
的數據類型
以及約束
Example
CREATE TABLE teaches(
id varchar(5),
course_id varchar(8),
section_id varchar(8),
semester varchar(6),
year numeric(4,0),
PRIMARY KEY (id, course_id, section_id, semester, year),
FOREIGN KEY (course_id, section_id, semester, year) REFERENCES section,
FOREIGN KEY (id) REFERENCES instructor
);
Basic DML
Insert
INSERT INTO instructor VALUES (10211, 'Smith', 'Biology', 66000);
Delete
- Delete a
Relationship
DROP TABLE r;
- Delete
Tuple
DELETE FROM student;
可以使用
DELETE FROM r
來刪除關系中的所有元組
,但保留關系的模式定義
Alter
ALTER TABLE r ADD attribute domain;
ALTER TABLE r DROP attribute;
某些
數據庫
僅僅支持刪除整個關系
,而不支持刪除關系中的某個屬性
。
Basic Structure
SQL
的 基本查詢
由 SELECT
,FROM
,WHERE
三大 子句
所構成。
Single-Relation query
SELECT name
FROM instructor;
數學
上的關系
本質是集合
,不允許重復元素
。但
維護元素的唯一性
的代價
非常大,在數據庫
中則是允許關系中的元素發生重復
all
和distinct
對於
SELECT
來說,去重選項
默認為ALL
即表示不去重
。SELECT ALL dept_name FROM instructor;
也可以手動指定為
distinct
表示需要進行去重
SELECT DISTINCT dept_name FROM instructor;
使用 WHERE子句
可以從 FROM子句的結果關系
中 篩選
出 使得謂詞為True的元組
SELECT name
FROM instructor
WHERE deptname 'Comp.Sci.' AND salary > 70000;
Multi-Relation query
General Form
多表查詢
的 通用格式
SELECT attribute1, attribute2, ..., attributen
FROM r1, r2, ..., rn
WHERE P;
其中,每種 子句
的作用如下:
SELECT子句
:從WHERE子句給出的元組
中投影
指定的屬性列表
FROM子句
:給出作為數據輸入的
的關系列表
WHERE子句
:過濾出
使得謂詞
為True
的FROM子句中的元組
數據庫
執行查詢
的順序
與書寫順序
不太一樣:$FROM \to WHERE \to SELECT$可以將
SELECT子句
看作是一種語言學方面的結構提前
,這種形式提前
有利於我們快速地了解輸出的結構是什麽
WHERE子句
的默認值
為WHERE true
FROM子句
會為關系列表
中的所有關系
都進行笛卡爾積運算
,通常會產生一個非常巨大的結果關系
實際上,大部分情況下並不會真的
生成
關系列表中所有關系的笛卡爾積
。因為
查詢優化器
會事先過濾
掉大部分不可能滿足WHERE子句的元組
,使得FROM子句
輸出的結果關系
規模大幅度減小
對於
FROM instructor, department
來說,就是對instructor關系
和department關系
進行笛卡爾積
作為結果關系
Match Condition Specified by Where Clause
通過 Where子句
指定 匹配條件
SELECT name, course_id
FROM instructor, teaches
WHERE instructor.ID = teaches.ID;
當
屬性名
僅出現在FROM子句所指定的關系
的一個關系
中時,可以省略掉屬性名的全限定前綴
。當然,
總是為屬性名寫上關系名前綴
是沒有錯的。n.b. 上述的
匹配條件
並不會查詢出沒有教授任何課程的教師
。如果期望顯示出這些教師,則應當使用
外連接
Match Condition Specified by Natural Join
Natural Join: Compare all the common attributes
自然連接 (Natural Join)
:將輸入的 2個關系
中 共有屬性 (Common Condition) 的值
均相等
的元組
作為 輸出結果
下列 查詢
的 等價形式
SELECT name, course_id
FROM instructor, teaches
WHERE instructor.ID = teaches. ID
SELECT name, course_id
FROM instructor NATURAL JOIN teaches
實際上,可以將
NATURAL JOIN語句
看作是一種形式的宏展開 (Macro Expansion)
。先查找
兩個關系的共有屬性
,然後改寫
成WHERE子句
形式的謂詞
:WHERE S.common_attribute_1 = T.common_attribute_1 AND S.common_attribute_2 = T.common_attribute_2 ... AND S.common_attribute_n = T.common_attribute_n
Natural Join: Specify specific common attributes
首先,考慮下面這個 查詢
-- Query 1
SELECT name, title
FROM instructor NATURAL JOIN teaches, course
WHERE teaches.course_id = course.course_id;
n.b.
teaches.course_id
追根溯源來自teaches
,我們是無法直接引用
自然連接的結果關系
的,但可以直接引用
參與自然連接的屬性
,因為自然連接的結果關系
中的屬性
正來自這些單個關系的屬性
。
但是,該查詢 並不等價於
下列這個 查詢
-- Query 2
SELECT name, title
FROM instructor NATURAL JOIN teaches NATURAL JOIN course
instructor關系
,teaches關系
,course關系
均有dept_name屬性
。對於
Query 1
:則只需要考慮這兩個關系的
dept_name屬性
的相等
,而course關系的dept_name屬性
可以與他們不同。也就是說,該查詢會顯示出教師所講授的課程
不是教師所在系的課程
對於
Query 2
:該查詢只顯示
教師所教的課程
就是該教師所在系的課程
出現該問題的原因在於,在將 instructor NATURAL JOIN teaches
和 course
進行 自然連接
時,course
中存在 我們不希望使用的共同屬性
即 dept_name
course關系
共有2個共同屬性
:course_id
,dept_name
對此,我們可以通過 帶指定屬性列表的自然連接 (Natural Join with Specific Arrtibute List)
來改寫 Query 2
-- Query 3
SELECT name, title
FROM (instructor NATURAL JOIN teaches) JOIN course USING (course_id)
這樣,Query 3
等價於 Query 1
。
n.b. 同樣的道理,可以再將
NATURAL JOIN
視為JOIN relation USING attribute_list
的宏展開
。默認情況下,
relation_1 NATURAL JOIN relation_2
可以展開為relation_1 JOIN relation_2 USING (common_attribute_list)
Basic Operation
Rename Operation
更名運算 (Rename Operation)
用於為 實體
指定 標識符
。
它主要有以下幾種作用:
修改
輸出關系
中的長屬性名
修改為短屬性名
SELECT instructor_name AS inst_name FROM instructor
為
運算中間結果
指定標識符
,以便後續對它的引用
用於
區分
涉及自身關系的笛卡爾積運算
SELECT DISTINCT T.name FROM instructor AS T, instructor AS S WHERE T.salary > S.salary AND S.dept_name = 'Biology'
n.b. 可以將
T
和S
看作是instructor關系
的2份拷貝
實際上,並不會真的拷貝2份關系。
T
和S
只不過是對instructor
的引用
而已。像
T
和S
這樣用於重命名關系的標識符
被稱為相關名稱 (Correlation Name)
,或表別名 (Table Alias)
或相關變量 (Correlation Variable)
或元組變量 (Tuple Variable)
String Operation
Pattern Matching
SELECT dept_name
FROM department
WHERE building LIKE '%Watson%';
通過使用 LIKE操作符
可以實現 模式匹配 (Pattern Matching)
,使用如下 字符
來 定義模式
:
百分號 (%)
:匹配任意子串
下劃線 (_)
:匹配任意一個字符
n.b. 盡管
SQL標準
中要求字符串的相等運算
是大小寫敏感的
。但
某些數據庫
,如MySQL
和SQL Server
卻在匹配字符串
時不區分大小寫
!關於
字符串相等性測試
的具體詳情,應當查閱Manual
Escape Character
LIKE 'ab\%cd%' ESCAPE '\'
使用 ESCAPE關鍵字
定義 轉義字符
為 \
Order Operation
SELECT *
FROM instructor
WHERE dept_name = 'Physics'
ORDER BY salary DESC, name ASC;
ORDER BY子句
的默認值
為asc
,而desc
需要手動指定。
Between Operation
-- Query 1
SELECT name
FROM instructor
WHERE salary BETWEEN 9000 AND 10000;
-- Query 2
SELECT name
FROM instructor
WHERE salary >= 9000 AND salary <= 10000;
Query 1
等價於 Query 2
n.b. 可以將
Between運算符
和Not Between運算符
視為基於不等式比較運算符寫法
的宏展開
。但使用
Between
和Not Between
使得查詢
更加清晰
,而且更不容易錯寫
Multi-Dimensional Tuple
考慮下列 包含多個AND的相等性測試語句
-- Query 1
SELECT name, course_id
FROM instructor, teaches
WHERE instructor.ID = teaches.ID AND dept_name = 'Biology'
以及
-- Query 2
SELECT name, course_id
FROM instructor, teaches
WHERE (instructor.ID, dept_name) = (teaches.ID, 'Biology')
Query 1
等價於 Query 2
作為一種更為
抽象
的考慮。我們可以認為常規所書寫的相等語句
本質上就屬於1維元組
。而如果需要同時測試
多個屬性的某種比較關系
,可以使用向量/元組
。上述僅僅是
測試
相等關系
。同理,也可以用於測試
偏序關系
。(x1, y1) < (x2, y2)
Set Operation
SQL標準
中所定義的 集合運算
都是基於 數學的集合論語義
的:所有的集合運算
會 自動去重
實際上,
SQL
的集合運算
有兩套版本
自動去重的默認的distinct版本
:intersect (distinct)
,union (distinct)
,except (distinct)
不帶去重的all版本
:intersect all
,union all
,except all
Intersect Operation
(SELECT course_id
FROM section
WHERE semester='Fall' AND year = 2009
) INTERSECT
(SELECT course_id
FROM section
WHERE semester='Spring' AND year = 2010
)
Union Operation
(SELECT course_id
FROM section
WHERE semester = 'Fall' AND year = 2009
) UNION
(SELECT course_id
FROM section
WHERE semester = 'Spring' AND year = 2010
)
Except Operation
(SELECT course_id
FROM section
WHERE semester = 'Fall' AND year = 2009
) EXCEPT
(SELECT course_id
FROM section
WHERE semester = 'Spring' AND year = 2010
)
Null
The type of operation involved
The type of operation involved | Rule |
---|---|
算術表達式 |
任何 算術表達式 的 輸入為空 ,則 輸出為空 |
邏輯表達式 |
任何 涉及空值的邏輯表達式 的 輸出為未知 (unknown) n.b. unknown 並不是 null 或 not null ,也不是 true 和 false 。unknown 是 區別於true和false的第三種邏輯值 基本邏輯運算 對於 unknown 定義了 特殊規則 fasle AND unknown = false ,true OR unknown = true ,NOT unkown = unknown |
n.b.
SQL標準
定義的邏輯值
有3種:true
,false
,unknown
。註意
while子句
的語義
為:過濾出
使得謂詞為true的哪些元組
n.b.
未知 (unknown)
也不是空 (null)
。
unknown
不可以用is null
或is not null
來測試。某些
具體實現
提供了is unknown
來進行測試。
Equality Test
Equality Test | Rule |
---|---|
元組的相等性測試 |
在測試 兩個元組的屬性值 是否相等時,如果 屬性值 均為 null ,則 屬性值視為相同 n.b. 如果 只想保留這樣的相同元組的一份拷貝 ,可以使用 SELECT DISTINCT |
謂詞的相等性測試 |
在測試 謂詞 null = null 時,將返回 unknown ,而不是 true |
n.b. 可以認為,
元組的相等性測試
實際上執行的是另一套特殊規則
:它為null = null
返回true
而
謂詞的相等性測試
則:為null = null
返回unknown
Aggregate Function
聚集函數 (Aggregate Function)
:以 值的集合
作為輸入,返回 單個值
。
Intrinsic Aggregate Function | Node |
---|---|
$avg()$ | |
$min()$ | |
$max()$ | |
$sum()$ | 忽略 null |
$count()$ | 為 null 返回 0 |
Basic Aggregate
SELECT avg(salary)
FROM instructor
WHERE dept_name = 'Comp.Sci.';
計算 某個關系中的元組個數
SELECT count(*)
FROM course;
若需要在 聚集操作
之前
進行 去重
,則可以使用 distinct關鍵字
SELECT count(DISTINCT ID)
FROM teaches
WHERE semester = 'Spring' AND year = '2010';
n.b. 可以認為,對於
內置的5個聚集函數
而言,默認的去重選項
是all
n.b.
SQL標準
並不允許為count(*)
使用distinct
。但卻可以為min
和max
使用distinct
。盡管這些distinct
並不會改變運算結果
!
Grouped Aggregate
分組聚集 (Grouped Aggregate)
:可以先對 某個關系中的元組
進行 分組
,然後再 分別地
對 每個分組
進行 聚集操作
。
SELECT dept_name, avg(salary) AS avg_salary
FROM instructor
GROUP BY dept_name;
可以認為,默認情況下
省略
GROUP BY子句
意味著:將整個關系的所有元組
分為唯一的一組
。
考慮一個 錯誤的查詢例子
-- This is a wrong query example
SELECT dept_name, ID, avg(salary)
FROM instructor
GROUP BY dept_name;
該 查詢
的問題在於,我們使用 dept_name屬性
將 instructor關系的元組
按 系
進行 分組
,之後的 avg(salary)
計算的是 某個系的所有教師的平均工資
。
而 ID屬性
的問題在於,對於 某個系
來說, 該系中有許多的教師
,如果確實需要 輸出ID
,那麽究竟要輸出 該系的所有教師中的哪一個教師的ID
?
綜上,我們對於 GROUP BY
有一個規則:出現在 SELECT子句
中,但沒有被 聚集
的 屬性
。必須出現在 GROUP BY
中。
Grouped Aggregate with Having Clause
SELECT dept_name, avg(salary) as avg_salary
FROM instructor
GROUP BY dept_name
HAVING avg(salary) > 42000;
HAVING 子句
在 Grouped Aggregate輸出結果
之後
才進行 過濾
。
換句話說,
HAVING
必須在GROUP BY
的分組形成後
才能執行
同理,對 HAVING
也有類似 GROUP BY
的規則 :出現在 HAVING子句
中,但沒有被 聚集
的 屬性
。必須出現在 GROUP BY
中。
可以將
HAVING子句
和WHERE子句
作類比。
WHERE
:針對元組
HAVING
:針對分組
現在,我們可以這樣看待
標準的SELECT-FROM-WHERE查詢
為:將某個關系的分組
分為唯一的一組
,且分組過濾條件
為HAVING true
Nested Subquery
我們知道 SELECT-FROM-WHERE
返回類型為 關系
,而 FROM子句
的輸入類型也為 關系
,那麽 嵌套子查詢 (Nested Subquery)
應當是合理的。
in
和not in
SELECT DISTINCT course_id
FROM section
WHERE semester = 'Fall' AND year = 2009
AND course_id IN (SELECT course_id
FROM section
WHERE semester = 'Spring' AND year = 2010;
);
all
和any/some
-- Query 1
SELECT DISTINCT T.name
FROM instructor AS T, instructor AS S
WHERE T.salary > S.salary AND S.dept_name = 'Biology';
n.b. 這種實現方式中,必須要加
DISTINCT
:因為FROM子句
輸出的結果關系
為2個關系的笛卡爾積
,此時結果關系
中滿足WHERE條件的元組
會重復
等價於
-- Query 2
SELECT name
FROM instructor
WHERE salary > some(SELECT salary
FROM instructor
WHERE dept_name = 'Biology'
);
n.b. 兩條關於
全稱量詞和特稱量詞
的等價性規則
:
= some
等價於in
,但<> some
不等價於not in
<>all
等價於not in
,但= all
不等價於in
exists
exists
:若作為exists參數的子查詢
產生的結果關系
是非空的
時,則返回true
SELECT course_id FROM section AS S WHERE semester = 'Fall' AND year = 2009 AND EXISTS(SELECT * FROM section AS T WHERE semester = 'Spring' AND year = 2010 AND S.course_id = T.course_id; );
n.b. 該例子中,還有一個需要註意的地方:我們在
子查詢
中引用
了子查詢外部的標識符
S.course_id
。我們稱
這樣的子查詢
為相關子查詢 (Correlated Subquery)
:使用了來自外層查詢的相關名稱
的子查詢
關於
子查詢
中對標識符
的引用規則
,可以類比於PL中的變量作用域規則
not exists
可以用於 測試
子查詢的結果集
中 是否不存在元組
。
我們可以利用這個 特性
來模擬 集合的包含關系
-- use not exists to represent subset relationship
SELECT S.ID, S.name
FROM student AS S
WHERE NOT EXISTS(
(SELECT course_id
FROM course
WHERE dept_name = 'Biology'
)
EXCEPT
(SELECT T.course_id
FROM takes AS T
WHERE S.ID = T.ID
)
);
unique
unique
:若 作為unique參數的子查詢
產生的 結果關系
中 不存在重復元組
,則返回 true
。它為 空集
返回 true
。
SELECT T.course_id
FROM course AS T
WHERE UNIQUE(SELECT R.course_id
FROM section AS R
WHERE T.course_id = R.course_id
AND R.year = 2009
)
實際上,可以利用
count()
將exists
,not exists
,unique
和not unique
進行宏展開
SELECT T.course_id FROM course AS T WHERE 1 >= (SELECT count(R.course_id) FROM section AS R WHERE T.course_id = R.course_id AND R.year = 2009 )
解釋:
WHERE子句
將對T關系中的每個元組
進行測試
,判斷嵌套子查詢
中與該元組相等的元組的個數
是否小於等於1
該例子中,通過
course_id
作為主鍵
來判斷兩個元組是否相等
。而
AND R.year = 2009
僅僅是附加的過濾條件
n.b.
unique
判斷元組是否重復
是基於元組的相等性測試
的。故如果兩個元組中的某個元組的某個屬性
為null
,則這兩個元組
不相等
。換句話說,使用
unique
判斷某些屬性為空的元組
是否重復
是錯誤的:在這種情況下,盡管
某個元組確確實實有多個副本
,但該元組有一個屬性為空
,則元組的相等性測試
將永遠為false
,進而使得unique
永遠返回true
FROM子查詢
可以利用 FROM子查詢
來 去除
HAVING子句
-- Query 1
SELECT dept_name, avg(salary) as avg_salary
FROM instructor
GROUP BY dept_name
HAVING avg(salary) > 42000;
等價於
-- Query 2
SELECT dept_name, avg_salary
FROM (SELECT dept_name, avg(salary)
FROM instructor
GROUP BY dept_name) AS dept_avg(dept_name, avg_salary)
WHERE avg_salary > 42000;
該 轉化
的思想在於:HAVING子句
實際上的 執行時機
是在 GROUP BY產生結果關系之後
,再對 結果關系(也就是一些分組)
進行 過濾
。
因此,我們可以 先獲得作為結果關系的這些分組
,然後再 對這些分組進行過濾
。
WITH子句
WITH子句
:用於定義 僅對包含with子句的查詢可見
的 臨時關系
WITH dept_total(dept_name, value) AS (SELECT dept_name, sum(salary)
FROM instructor
GROUP BY dept_name),
dept_total_avg(value) AS (SELECT avg(salary)
FROM dept_total)
SELECT dept_name
FROM dept_total, dept_total_avg
WHERE dept_total.value >= dept_total_avg.value;
n.b. 使用
WITH子句
可以非常有效地
去除嵌套子查詢
。如果可能,
應當盡量使用WITH子句來消除嵌套子查詢
標量子查詢 (Scalar Subquery)
SQL
會 自動地
將 僅含有單屬性單元組的關系
解包
為 單個值
。
SELECT name
FROM instructor
WHERE salary < (SELECT avg(salary)
FROM instructor)
n.b. 從
本質
上來說,標量子查詢
返回的數據類型
是確確實實的關系
。只不過,在
SQL
實現中可以自動地對這種類型的關系
進行拆包
。這類似於Java
中的Auto Boxed/Unboxed
SQL編譯器
並無法檢測
標量子查詢
是否真的符合要求。如果在
運行時
發現需要輸入單個值的地方
所使用的標量子查詢
包含多個值
,則會導致運行時錯誤
Modifying Operation
Delete
考慮該例子的 語句執行順序
-- This is a wrong example
DELETE FROM instructor
WHERE salary < (SELECT avg(salary)
FROM instructor)
請註意,我們所編寫的SQL語句
應當 滿足
:在該語句進行 修改性操作
之前,必須先 測試所有的元組
,將 符合測試條件的元組
加入到 待刪除列表
,之後再 一次性地
執行 修改性操作
。
註意:這裏所說的 修改性操作
不僅僅是指 Delete
,包括 Insert
和 Alter
都有 類似的問題
該問題類似於
ArrayList的並發修改問題
,當我們對正在遍歷中的列表
進行修改性操作
時,必須保證最終的效果
不依賴於修改性操作所執行的順序
給出
MySQL 8.0
以上版本將拒絕執行該語句
並且返回錯誤
1093 - You can’t specify target table ‘instructor’ for update in FROM clause
Insert
常用的插入操作
INSERT INTO course(course_id, title, dept_name, credits)
VALUES ('CS-437', 'Database Systems', 'Comp. SCi.', 4);
INSERT INTO instructor
SELECT ID, name, dept_name, 18000
FROM student
WHERE dept_name = 'Music' AND total_cred > 144;
-- This is a wrong example
INSERT INTO student
SELECT *
FROM student
該語句 可能會導致無限遞歸插入
,這取決於 數據庫的具體實現
。
經過實際測試,
MySQL 8.0
以上可以正常地執行該語句
,並且查詢會終止
。它的
語義
為:將表中的所有元組
按照順序
克隆一份插入到表的末尾
Alter
當 update語句
之間 受語句執行順序
所 影響
時,可以使用 case結構
-- SQL 1
UPDATE instructor
SET salary = salary * 1.03
WHERE salary > 100000
-- SQL 2
UPDATE instructor
SET salary = salary * 1.05
WHERE salary <= 100000
為了 正確地表達語義
,我們可以使用 case
結構來 正確表述執行順序之間相互影響的語句
UPDATE instructor
SET salary = CASE
WHEN salary <= 100000 THEN salary * 1.05
ELSE salary * 1.03
END