| 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
CREATE TABLE r
(Attribute1 Domain1,
Attribute2 Domain2,
...,
Attributen Domainn,
<Integrity Constraint>,
...,
<Integrity Constraint>);
域 (Domain):用於指定與域相關聯的屬性的數據類型以及約束
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
);
INSERT INTO instructor VALUES (10211, 'Smith', 'Biology', 66000);
RelationshipDROP TABLE r;
TupleDELETE FROM student;
可以使用
DELETE FROM r來刪除關系中的所有元組,但保留關系的模式定義
ALTER TABLE r ADD attribute domain;
ALTER TABLE r DROP attribute;
某些
數據庫僅僅支持刪除整個關系,而不支持刪除關系中的某個屬性。
SQL 的 基本查詢 由 SELECT,FROM,WHERE 三大 子句 所構成。
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;
多表查詢 的 通用格式
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關系進行笛卡爾積作為結果關系
通過 Where子句 指定 匹配條件
SELECT name, course_id
FROM instructor, teaches
WHERE instructor.ID = teaches.ID;
當
屬性名僅出現在FROM子句所指定的關系的一個關系中時,可以省略掉屬性名的全限定前綴。當然,
總是為屬性名寫上關系名前綴是沒有錯的。n.b. 上述的
匹配條件並不會查詢出沒有教授任何課程的教師。如果期望顯示出這些教師,則應當使用
外連接
自然連接 (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
首先,考慮下面這個 查詢
-- 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)
更名運算 (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)
SELECT dept_name
FROM department
WHERE building LIKE '%Watson%';
通過使用 LIKE操作符可以實現 模式匹配 (Pattern Matching),使用如下 字符 來 定義模式:
百分號 (%):匹配 任意子串下劃線 (_):匹配 任意一個字符n.b. 盡管
SQL標準中要求字符串的相等運算是大小寫敏感的。但
某些數據庫,如MySQL和SQL Server卻在匹配字符串時不區分大小寫!關於
字符串相等性測試的具體詳情,應當查閱Manual
LIKE 'ab\%cd%' ESCAPE '\'
使用 ESCAPE關鍵字 定義 轉義字符 為 \
SELECT *
FROM instructor
WHERE dept_name = 'Physics'
ORDER BY salary DESC, name ASC;
ORDER BY子句的默認值為asc,而desc需要手動指定。
-- 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使得查詢更加清晰,而且更不容易錯寫
考慮下列 包含多個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)
SQL標準 中所定義的 集合運算 都是基於 數學的集合論語義 的:所有的集合運算 會 自動去重
實際上,
SQL的集合運算有兩套版本
自動去重的默認的distinct版本:intersect (distinct),union (distinct),except (distinct)不帶去重的all版本:intersect all,union all,except all
(SELECT course_id
FROM section
WHERE semester='Fall' AND year = 2009
) INTERSECT
(SELECT course_id
FROM section
WHERE semester='Spring' AND year = 2010
)
(SELECT course_id
FROM section
WHERE semester = 'Fall' AND year = 2009
) UNION
(SELECT course_id
FROM section
WHERE semester = 'Spring' AND year = 2010
)
(SELECT course_id
FROM section
WHERE semester = 'Fall' AND year = 2009
) EXCEPT
(SELECT course_id
FROM section
WHERE semester = 'Spring' AND year = 2010
)
| The type of operation involved | Rule |
|---|---|
算術表達式 |
任何 算術表達式 的 輸入為空,則 輸出為空 |
邏輯表達式 |
任何 涉及空值的邏輯表達式 的 輸出為未知 (unknown)<br />n.b. unknown 並不是 null或 not null,也不是 true 和 false。<br />unknown 是 區別於true和false的第三種邏輯值<br /><br />基本邏輯運算 對於 unknown 定義了 特殊規則<br />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 | Rule |
|---|---|
元組的相等性測試 |
在測試 兩個元組的屬性值 是否相等時,如果 屬性值 均為 null,則 屬性值視為相同<br />n.b. 如果 只想保留這樣的相同元組的一份拷貝,可以使用 SELECT DISTINCT |
謂詞的相等性測試 |
在測試 謂詞 null = null 時,將返回 unknown,而不是 true |
n.b. 可以認為,
元組的相等性測試實際上執行的是另一套特殊規則:它為null = null返回true而
謂詞的相等性測試則:為null = null返回unknown
聚集函數 (Aggregate Function):以 值的集合 作為輸入,返回 單個值 。
| Intrinsic Aggregate Function | Node |
|---|---|
| $avg()$ | |
| $min()$ | |
| $max()$ | |
| $sum()$ | 忽略 null |
| $count()$ | 為 null 返回 0 |
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個聚集函數而言,默認的去重選項是alln.b.
SQL標準並不允許為count(*)使用distinct。但卻可以為min和max使用distinct。盡管這些distinct並不會改變運算結果!
分組聚集 (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 中。
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
我們知道 SELECT-FROM-WHERE 返回類型為 關系,而 FROM子句 的輸入類型也為 關系,那麽 嵌套子查詢 (Nested Subquery) 應當是合理的。
in 和 not inSELECT 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
)
);
uniqueunique:若 作為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編譯器並無法檢測標量子查詢是否真的符合要求。如果在
運行時發現需要輸入單個值的地方所使用的標量子查詢包含多個值,則會導致運行時錯誤
考慮該例子的 語句執行順序
-- 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 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以上可以正常地執行該語句,並且查詢會終止。它的
語義為:將表中的所有元組按照順序克隆一份插入到表的末尾
當 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