| 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