{% raw %} $$ \boxed{@} % Color % \newcommand\c[2]{\textcolor{#1}{#2}} \newcommand\r[1]{\textcolor{red}{#1}} \newcommand\g[1]{\textcolor{green}{#1}} \newcommand\b[1]{\textcolor{blue}{#1}} \newcommand\red[1]{\textcolor{red}{#1}} \newcommand\blue[1]{\textcolor{blue}{#1}} \newcommand\green[1]{\textcolor{green}{#1}} \newcommand\black[1]{\textcolor{black}{#1}} \newcommand\white[1]{\textcolor{white}{#1}} \newcommand\cyan[1]{\textcolor{cyan}{#1}} \newcommand\magenta[1]{\textcolor{magenta}{#1}} \newcommand\yellow[1]{\textcolor{yellow}{#1}} \newcommand\orange[1]{\textcolor{orange}{#1}} \newcommand\lime[1]{\textcolor{lime}{#1}} \newcommand\pink[1]{\textcolor{pink}{#1}} \newcommand\darkgray[1]{\textcolor{darkgray}{#1}} \newcommand\gray[1]{\textcolor{gray}{#1}} \newcommand\lightgray[1]{\textcolor{lightgray}{#1}} \newcommand\brown[1]{\textcolor{brown}{#1}} \newcommand\olive[1]{\textcolor{olive}{#1}} \newcommand\purple[1]{\textcolor{purple}{#1}} \newcommand\teal[1]{\textcolor{teal}{#1}} \newcommand\violet[1]{\textcolor{violet}{#1}} \newcommand\hotpink[1]{\textcolor{hotpink}{#1}} \newcommand\blueviolet[1]{\textcolor{blueviolet}{#1}} \newcommand\navyblue[1]{\textcolor{navyblue}{#1}} \newcommand\peach[1]{\textcolor{Peach}{#1}} \newcommand\orangeRed[1]{\textcolor{OrangeRed}{#1}} \newcommand\salmon[1]{\textcolor{Salmon}{#1}} \newcommand\skyblue[1]{\textcolor{SkyBlue}{#1}} \newcommand\springreen[1]{\textcolor{SpringGreen}{#1}} \newcommand\aqua[1]{\textcolor{aqua}{#1}} \newcommand\navy[1]{\textcolor{navy}{#1}} \newcommand\silver[1]{\textcolor{silver}{#1}} \newcommand\fuchsia[1]{\textcolor{fuchsia}{#1}} \newcommand\maroon[1]{\textcolor{maroon}{#1}} \definecolor{luo}{RGB}{102,204,255} \definecolor{miku}{RGB}{57,197,187} \newcommand\luo[1]{\textcolor{luo}{#1}} \newcommand\miku[1]{\textcolor{miku}{#1}}
% Typography % \newcommand\a[1]{\begin{aligned}#1\end{aligned}} \newcommand\t[1]{\text{#1}} \newcommand\tb[1]{\text{\textcolor{blue}{#1}}} \newcommand\lb[1]{\left{\begin{aligned} #1 \end{aligned}\right.} \newcommand\lrb[1]{\lb{\rb{#1}}} \newcommand\rb[1]{\left.\begin{aligned} #1 \end{aligned}\right}} \newcommand\env[2]{\begin{#1}#2\end{#1}} \newcommand\step[1]{\textbf{ (#1) }}
% Misc % \newcommand\s[1]{{#1}} \newcommand\qed{\quad\square} \newcommand\define{\dot{=}} \newcommand\then{\implies} \newcommand\rounddown[1]{\lfloor{#1}\rfloor} \newcommand\roundup[1]{\lceil{#1}\rceil} \newcommand\graph[4]{#1 = (#2, #3) \quad |#2| = #4} \newcommand\G{G = (V, E) \quad |V| = n} \newcommand\so{\therefore} \newcommand\comment[1]{\quad\text{(#1)}} \newcommand\note[1]{\quad\text{(#1)}} \newcommand\bt[1]{\boxed{\text{#1}}} \newcommand\max[1]{\textbf{ max } {#1} } \newcommand\min[1]{\textbf{ min } {#1} } \newcommand\IF{\textbf{ IF }} \newcommand\if{\textbf{ if }} \newcommand\IS{\textbf{ IS }} \newcommand\is{\textbf{ is }} \newcommand\do{\textbf{ do }} \newcommand\dowhile{\textbf{ do while }} \newcommand\dountil{\textbf{ do until }} \newcommand\find{\textbf{ find }} \newcommand\until{\textbf{ until }} \newcommand\thereisa{\textbf{ There is a }} \newcommand\thereisan{\textbf{ There is an }} \newcommand\hasno{\textbf{ has no }} \newcommand\has{\textbf{ has }} \newcommand\but{\textbf{ but }} \newcommand\however{\textbf{ however }} \newcommand\AND{\textbf{ AND }} \newcommand\OR{\textbf{ OR }} \newcommand\NOT{\textbf{ NOT }} \newcommand\THEN{\textbf{ THEN }} \newcommand\IN{\textbf{ in }} \newcommand\NOTIN{\textbf{ NOT-IN }} \newcommand\assume{\textbf{ Assuming that: }} \newcommand\contradictory{\textbf{ Thus lead to contradiction }} \newcommand\proof{\textbf{Proof: }} \newcommand\st{\textbf{ such that }} \newcommand\hold{\text{ holds }} \newcommand\lhs{\text{ LHS }} \newcommand\rhs{\text{ RHS }} \newcommand\wlg{\text{ Without loss of generality }} \newcommand\nb{\text{ nota bene }} \newcommand\analogously{\text{ analogously }} \newcommand\viceversa{\textbf{ viceversa }} \newcommand\let{\textbf{ let }} \newcommand\as{\textbf{ as }} \newcommand\for{\textbf{ As for }} \newcommand\select{\textbf{ SELECT }} \newcommand\m[1]{\mathit{#1}} \newcommand+[1]{\mathcal{#1}} \newcommand\warnning[1]{\colorbox{Blue}{\textcolor{Yellow}{#1}}} \newcommand\error[1]{\colorbox{Black}{\textcolor{White}{#1}}} $$
{% endraw %}
$$ \bt{Join Expression} \lb{ & \t{Join Type} \lb{ & \t{inner join} \ & \t{left outer join} \ & \t{right outer join} \ & \t{full outer join} } \ & \t{Join Condition} \lb{ & \t{natural} \ & \t{using attribute_list} \ & \t{on <predicate>} } } $$
使用 natural 作为 连接条件 的 连接 (Join) 称为 自然连接 (Natural Join)。
我们前面讨论过,可以将
natura展开为using attribute_list展开为on <predicate>
指定对 参与连接的关系 进行 连接 时所使用的 属性列表 (Attribute List)
on条件 用于设置 参与连接的关系 的 元组匹配 的 通用谓词
从
功能范围来讲:on <Predicate>>using attribute_list>natural
我们考虑下面两个连接。
-- SQL 1
SELECT *
FROM student JOIN takes ON student.ID = takes.ID
-- SQL 2
SELECT *
FROM student NATURAL JOIN takes
SQL1 $\ne$ SQL 2 ,但它们的 结果关系 高度 相似,唯一的区别 是:SQL 1 的 结果关系 中, ID属性 出现 2次,分别为 student.ID 和 takes.ID
注意
on <predicate>的语义:<predicate>过滤出能参与连接的元组。注意
join的语义:最基本的连接 (Join)操作就是简单地将两个元组进行拼接。
自动去除相同的属性名是natural join的特性,join并不会这么做!
然而,对于 SQL 2来说,由于 student关系 和 takes关系 的 共有属性 仅有 ID,所以 连接条件 也是 student.ID = takes.ID。
但 Natural Join 会 自动地去除属性名相同的列,所以 ID属性 仅在 SQL 2的结果关系 中 出现1次
更确切地
-- SQL 3 equals SQL 1
SELECT *
FROM student, takes
WHERE student.ID = takes.ID
-- SQL 4 equals SQL 2
SELECT student.ID as ID, name, dept_name, tot_cred, course_id, sec_id, semester, year, grade
FROM student JOIN takes ON student.ID = takes.ID
内连接 (Inner Join) 是 默认的连接类型。
当我们写
JOIN时,实际上是省略了INNER的INNER JOIN。同理,
NATURAL JOIN=NATURAL INNER JOIN
考虑该语句
SELECT *
FROM student NATURAL JOIN takes;
该 语句 的 期望 是: 查询出所有学生的选课信息
但是,如果 某些学生没有选任何课,那么在 结果关系 中,我们将无法看到 这些学生。
如果我们 希望 在 结果关系 中,仍然显示出 没有选任何课的学生 的信息,并且 将它们的选课信息 显示为 null。
则可以使用 外连接
SELECT *
FROM student NATURAL LEFT OUTER JOIN takes;
外连接 (Outer Join):在 内连接 的基础上,通过在 结果关系 中 创建包含空值的元组 来 保留 哪些本来在内连接中丢失的元组
换句话说:
外连接会将那些有参与连接但不满足连接条件的元组的那些本该被内连接丢弃的元组重新捡回来,然后填充空值。注意一个细节,我们说
参与连接的元组指的是按照所属的连接表达式作为边界来划分的。另一个连接表达式和WHERE子句显然都属于边界之外。
外连接只会捡回那些参与了连接但不满足连接条件的元组,并为它们填充空值。如果某些元组根本没有参与这个外连接,那么显然这些元组不应该被这个外连接所捡回并填充空值。
外连接 的 3种 类型:
左外连接 (Left Outer Join):只 保留 出现在 左外连接运算符 左边的关系中的元组右外连接 (Right Outer Join):只 保留 出现在 右外连接运算符 右边的关系中的元组全外连接 (Full Outer Join):保留 两个关系中的元组相比来说,
内连接不保留两侧的关系中的任何关系中的元组
外连接的计算方法:可以先计算相应的内连接,然后向内连接的结果关系中加入应当被保留的元组
考虑该查询
-- SQL 1
SELECT *
FROM student LEFT OUTER JOIN takes ON student.ID = takes.ID;
SQL 1 $\ne$ SQL 2
-- SQL 2
SELECT *
FROM student LEFT OUTER JOIN takes ON true
WHERE student.ID = takes.ID;
SQL 1:结果关系 类似于 student LEFT NATURAL OUTER JOIN takes ,只不过 ID属性将出现2次。
SQL 2:由于指定 ON true 使得 参与连接的关系中的所有元组 都 满足连接条件,所以 外连接 不会 加入被内连接丢弃的元组并为它们填充空值
也就是说
由于没有元组因不满足连接条件而被内连接所丢弃,所以后续外连接也不会加入这些本来被内连接丢弃的元组并为它们填充空值
实际上,a LEFT OUTER JOIN b ON true 相当于 产生两个关系的笛卡尔积。
不要将
a LEFT OUTER JOIN b ON true和NATURAL LEFT OUTER JOIN弄混淆,NATURAL只不过是连接条件中的一种而已。
视图 (View):是一种 通过查询来定义的 虚关系 (Virtual Relation)。它在 概念层 包含 查询的结果,但 序关系并不 预先计算并存储,而是在 查询时临时进行计算 而得到。
该
定义暂时不考虑物化视图 (Materialized View)
所定义的 视图 的 模式 可以从 查询语句 中自动地被 推导 出来
CREATE VIEW faculty AS
SELECT ID, name, dept_name
FROM instructor;
当然,也可以指定 视图的属性名
CREATE VIEW departments_total_salary(dept_name, total_salary) AS
SELECT dept_name, sum(salary)
FROM instructor
GROUP BY dept_name;
物化视图 (Materialized View):和 非物化视图 的区别在于,物化视图 会 存储查询的结果关系,并 保证 当 实际关系 更新时,物化视图 也应当有 相应的维护策略。这个过程称为 物化视图的维护 (Materialized View Maintenance)
物化视图 的 维护策略 (Maintenance Strategy):
我们称 物化视图是 可更新的 (Updatable) :可以对 该物化视图 进行 修改性操作,且 这些操作可以正确地反映到实关系。
大部分的
物化视图都是不可更新的,仅能用于查询。这是因为
单纯从视图定义的查询语句很难翻译出等价的且合法的更新语句。
考虑该 视图定义
CREATE VIEW history_instructors AS
SELECT *
FROM instructor
WHERE dept_name = 'History';
如果我们 视图 执行 该语句
INSERT INTO history_instructors
VALUES ('25566', 'Brown', 'Biology', 100000)
则也 可能 存在 错误,原因在于:欲插入视图中的元组 无法在 视图中被查询出来。即 新值 不满足 视图的WHERE子句
SQL默认允许这种操作。如果希望禁止这种操作,可以在CREATE VIEW时添加WITH CHECK OPTION
事务 (Transaction):由 SQL语句的序列 组成,事务 拥有 ACID性
按照
SQL标准,任何一条单独的SQL语句都隐含地开始于一个新事务。从编程角度说,如果为了保证
多条SQL语句的原子性,则需要对SQL连接驱动做出相应的设置。如使用
JDBC时,首先关闭单条SQL语句的自动提交。
完整性约束 (Integrity Constraint):保证 授权用户 对 数据库的修改性操作 不会 破坏 数据库 的 数据一致性
常见的 完整性约束:
not null约束name varchar(30) NOT NULL
bugder numeric(12,2) NOT NULL
由于 空值 是 所有的域的成员,所以对于 SQL来说,空值 可以是 任何属性 的 合法值。
如果希望 禁止某个属性的值为空值,则可以设置 not null
unique约束UNIQUE(attribute_list)
unique约束 用于指定 某个属性列表 为 候选码:即在 关系 中,没有 任何两个元组 可以在 作为候选码的属性 上的 取值 都相同。
n.b.
候选码属性可以被设置为null(除非已经被声明not null),由于unique约束是基于元组的相等性测试的,它和unique结构对待null的方式相同。
check子句可以为 关系 定义 check(Predicate) ,使得 关系中的所有元组 都必须 满足该谓词。
可以使用
check来代替unique,not null
CREATE TABLE section
(course_id varchar(8),
sec_id varchar(8),
semester varchar(6),
year numeric(4,0),
building varchar(15),
room_number varchar(7),
time_slot_id varchar(4),
PRIMARY KEY (course_id, sec_id, semester, year),
CHECK(semester IN ('Fall', 'Winter', 'Spring', 'Summer'))
);
参照完整性 (Referential Integrity):在 某个关系的给定属性集上的取值 也 必须出现在 另一个关系的特定属性集的取值 。
若
关系$r_1$ 和关系$r_2$ 的属性集分别为 $R_1$ 和 $R_2$,主码分别为 $K_1$ 和 $K_2$。若对于 $R_2$ 中的
任何元组$t_2$ ,都存在 $t_1.K_1 = t_2.\b\alpha$则我们称:$R_2$的子集 $\alpha$ 为
参照关系$r_1$ 中 $K_1$ 的外码 (Foreign Key)即:我们要求 $r_2$ 中 $\alpha$ 上的
取值集合必须是 $r_1$ 中 $K_1$ 上的取值集合的子集。从这个意义上,
参照完整性又被称为子集依赖 (Subset Dependency)不同于
外码约束,参照完整性并不要求 $K_1$ 是 $r_1$ 的主码
CREATE TABLE section
(...
FOREIGN KEY(dept_name) REFERENCES department
ON DELETE CASCADE
ON UPDATE CASCADE
...
);
使用
REFERENCE子句时,要求属性列表必须是被参照关系的候选码。可以通过
PRIMARY KEY和UNIQUE来使得属性成为候选码 (Candidate Key)
可以为 参照完整性约束 中的 外码 设置 删除策略 和 更新策略
常见的几种策略有:
CASCADESET NULLSET DEFAULTn.b. 对于 元组的外码属性的相等性测试 有 特殊规则 :如果 某个元组的某个外码属性 为 null,则会 被视为满足约束。
对于 多步骤的事务 来说,可能在 事务的中间过程 暂时性地违反完整性约束 ,但在 事务之后的步骤 又 重新符合完整性约束。
此时,可以使用 initially deferred子句 来将 完整性约束检测 从 事务中间步骤 推迟到 事务结束时 再进行 检测
根据 SQL标准 来说,check 中允许定义 任何谓词。
如果 check的谓词 中包含 子查询,则该 check 是 复杂的。
考虑该例子
CHECK(time_slot_id IN (SELECT time_slot_id FROM time_slot))
为了维护 check约束,并非仅仅是 简单地在插入或更新元组时 进行 检查,在 子查询所涉及的关系 发生 变化 时,也需要进行 检查。
CREATE ASSERTION credits_earned_constraint CHECK(
NOT EXISTS(SELECT ID
FROM student
WHERE tot_cred <> (SELECT sum(credits)
FROM takes NATURAL JOIN course
WHERE student.ID = takes.ID
AND grade IS NOT NULL
AND grade <> 'F'
)
)
)
断言 (Assertion):断言 是 谓词 的一种形式,用于描述我们希望 数据库 总能 满足的条件。
域约束,参照完整性约束均属于断言目前大部分的
数据库对断言 (Assertion)的支持不完善。
维护一个断言的代价是非常大的。如果可能,可以使用触发器 (Trigger)来代替断言。
| Type | Node |
|---|---|
| date | 日历日期,如 1990-06-21 |
| time | 一天中的时间,如 8:42:26 |
| timestamp | date和time的组合,如 1990-06-21 8:42:26 |
CREATE TABLE student
(ID varchar(5),
name varchar(20) NOT NULL,
dept_name varchar(20),
tot_cred numeric(3, 0) DEFAULT 0
PRIMARY KEY(ID)
);
CREATE INDEX studentID_index ON student(ID)
$$ \t{Large OBject} \lb{ & \t{Character LOB} \ & \t{Binary LOB} } $$
CREATE TABLE demo
(
book_review clob(10KB)
image blob(10MB)
movie blob(2GB)
);
当
查询大对象时,数据库并非直接返回整个大对象。而是返回用于检索该大对象的定位器。这类似于
迭代器或文件指针,可以按小片段逐步地访问整个大对象
$$ \t{User Defined Type} \lb{ & \t{Distinct Type} \ & \t{Structured Data Type} } $$
独特类型 (Distinct Type)CREATE TYPE Dollars AS numeric(12, 2) FINAL;
CREATE TYPE Pounds AS numeric(12, 2) FINAL;
CREATE TABLE department
(dept_name varchar(20),
building varchar(15),
budget Dollars
);
final关键字是没有任何意义的。属于SQL标准的历史遗留问题
这里的关键点是,Dollars 是 独特类型,不能将 Pounds 类型赋值给 Dollars 类型
尽管
Dollars和Pounds基于相同的基本数据类型而定义的。
甚至,我们也无法将 Integer 赋值给 Dollars
-- This is wrong
department.budget + 20
-- This is right
cast(department.budget to numeric(12,2)) + 20
之前我们讨论过
域 (Domain),它和独特类型的区别在于:
域上可以声明约束和 定义默认值域并不是强类型的,基本类型相容的域可以相互赋值。
结构化数据类型 (Structured Data Type)可以创建 具有嵌套记录结构,数组,多重集 的 复杂数据类型
CREATE TABLE temp_instructor LIKE instructor
目录 (Catalog) 和模式 (Schema)用于为 关系 提供 命名空间,它们都是用于 限定 关系 的 限定名。
部分
数据库实现使用数据库术语 来代替目录。
如下例子
catelog5.univ_schema.course
每个
用户 (User)都有自己的默认目录 (Default Catalog)和默认模式 (Default Schema)。当
用户连接到数据库时,会为该连接自动地配置为默认目录和默认模式。这类似于
OS中为登陆OS的用户自动地切换当前目录为用户的主目录 (Home Directory)当
创建新用户时,会自动地创建与该用户名同名的模式在前面的例子中,我们都没有指定
catelog和schema。这是因为我们一直在使用
用户的默认目录和用户的默认模式。即直接用course来
定位course关系
授权 (Authorization):将某种 权限 (Privilege/Permission) 授予 某个用户 (User) 或 某个角色 (Role)
授予权限 和 撤销权限
GRANT SELECT ON department TO Amit, Satoshi;
REVOKE SELECT ON department FROM AMit, Satoshi;
授权的 最细粒度 为 关系的属性 :SQL标准 允许对 关系的某个属性 授权,但不允许对 关系的某个特定元组 进行授权。
GRANT UPDATE(budget) ON department TO Amit, Satoshi;
角色 (Role):即 权限(Permission)的集合。可以将 角色 授予 用户,使得 用户 获得 这些权限
使用 角色 的两点好处:
权限集 给 角色,然后再把 角色 授予给 用户,避免对 每个用户 都繁琐的授予 权限集标识 用户的身份,同时可以避免 所有相同身份的人使用同个用户 导致 安全隐患CREATE ROLE instructor;
GRANT SELECT ON takes TO instructor
GRANT instructor TO Amit
对于 仅有查看Geology系权限的工作人员,尽管 它没有权限查看其他系的元组,下列 语句 仍然是 合法的
CREATE VIEW geo_instructor AS
(SELECT *
FROM instructor
WHERE dept_name = 'Geology'
);
SELECT *
FROM geo_instructor
这表明,创建某个视图的用户 并不需要 获得该视图上的所有权限。
该 用户 在 该视图上所获得的权限 并不会 多余它已有的权限
即
危险的用户无法通过视图,函数或过程来获得超越它本有的权限的权限
用户 在 创建外码时,必须要拥有 被参照关系 的 REFERENCE权限。
GRANT REFERENCES (dept_name) ON department TO Mariano;
一个很关键在原因在于:所创建的外码 会对 被参照关系 的 删除和更新 产生 阻碍。
如果 某个用户 创建了许多外码约束,那么那个 被参照关系 在 删除和更新 时会 背负许多毫无意义的外码约束,使得因为 违反外码约束 而 无法进行 删除和更新。
因此,只有拥有被参照关系的REFERENCES权限的用户 才可以 创建引用该关系的外码。
类似地,
CHECK约束和REFERENCES约束一样,都要求用户拥有所引用关系的REFERENCE权限
SQL 默认 禁止 转授 授予某个用户的权限。
如果希望 授予Amit的SELECT权限,Amit仍然可以转授该权限给其他用户,则使用 WIGH GRANT OPTION
GRANT SELECT ON department TO Amit WITH GRANT OPTION;
用户 和 角色 之间的 权限授予 用 授权图 (Authorization Graph) 来描述。
考虑如下情况,如果 危险用户A 从 DBA 获得 某权限,然后 危险用户A 将 该权限转授 危险用户B。
同时,危险用户B 再将 该权限 转授 危险用户A。
也就是说,形成了一个 转授环。
graph TD;
dba((DBA)) --> a((A))
a --> b((B))
b --> a
使用 授权图 可以 处理 这种情况
我们规定,用户具有权限的充分必要条件 是 从授权图的根节点(DBA) 到 该用户的顶点 之间要存在 路径。
对于上述的情况,当我们 撤销授予用户A的权限时,由于 用户B的权限 是 用户A转授给它的,所以,实际上 撤销的路径为 $dba \to a \to b$
于是 授权图 变成
graph TD;
dba((DBA))
a((A))
b((B))
可以使用 restrict 来 阻止 级联收回,如果 存在级联收回,则 不执行任何收权动作
REVOKE SELECT ON department FROM amit, Satoshi RESTRICT
如果确实希望 只收回用户A的权限,但 不收回用户A授予用户B的权限。
则不应当使用 上述的权限拓扑,DBA 应当将 权限 授予 角色,然后将 角色 授予 用户。即
graph TD;
dba((DBA)) --> role(instructor)
role --> a((A))
role --> b((B))
当需要 仅撤销用户A的权限时,只需要 简单地撤销用户A的角色instructor即可
graph TD;
dba((DBA)) --> role(instructor)
a((A))
role --> b((B))
n.b. 作为一种规范,如果可能的话,
权限应该由角色来授予,而不是由特定的用户授予 !