第四章 中级SQL
4.1 连接表达式
SQL提供了连接运算的其他形式,通过这些形式能在结果中包含被自然连接排除在外的元组。
4.1.1 连接条件
SQL使用on
来在参与连接的关系上设置通用的谓词,和where
相似,但在外连接运算中表现结果与where
不同。
on
的用法如下。
select * from student join takes on student.ID = takes.ID;
上面的on
条件表明,若来自student的元组的ID
属性和来自takes的元组的ID
属性相同,则把它们连接起来,作为结果的元组。
上述结果虽然和下列语句在逻辑上相同
select * from student natural join takes;
但是on
语句并不取出重复的元组,也就是说结果关系中会出现两个ID
属性,student.ID
和takes.ID
实际上,等价语句是
select * from student, takes where student.ID = takes.ID;
4.1.2 外连接
外连接的引入
考虑查询“找出所有学生的选课信息”,如果使用如下查询语句。
select * from student natural join takes;
此时,对于那些没有选课的学生来说,即在takes中没有对应的ID,那么这个学生的选课结果不会被显示,而我们希望即使学生没有选课就设其选课信息为null
。此时,我们便引入了外连接。
总的来说,外连接在内连接的结果上通过在一些属性上设置null
来保留原来在内连接的结果上不匹配的元组。
外连接有三种,左外连接、右外连接和全外连接。
- 左外连接。先做内连接,然后把左边关系中未被包含的元组包含进去,对于这些元组在右边关系的项被置为
null
。 - 右外连接。先做内连接,然后把右边关系中未被包含进去的元组包含进去,对于这些元组的左边关系中的项被置为
null
。 - 全外连接。左外连接和右外连接的并集。
使用外连接后,查询“找出所有学生的选课信息”可以被如下表示。
select *
from student left outer join takes;
使用左外连接后,即使存在一些不选课的学生,其也被包含在结果中,只不过其在选课信息的值被置为null
。
相似的,右外连接和全外连接表示为right outer join
和full outer join
注意,on和where在外连接上表现得结果是不一样的。在外连接中,on作用于内连接的结果,也就是对被补上null然后插入的元组不起作用。但where是在内连接和补上null插入后得到的结果上发生作用。例如,上面的查询等价于
select *
from student left outer join takes on student.ID = takes.ID;
但下面的查询
select *
from student natural left outer join takes
where student.ID = takes.ID;
不存在包含了空值属性的元组。
4.2 视图
4.2.1 视图的定义
很多时候,我们并不希望让所有用户看到整个关系的逻辑模型。出于安全考虑,需要向用户隐藏特定数据。因此,SQL中引入了视图。视图是通过查询语句定义的,但是,其中并不预先包含数据,而是在使用过程中才通过定义它的查询语句计算出来。
SQL使用create view
来定义视图
create view view_name as <query expression>
view_name
是视图名称,<query expression>
是任何合法的查询语句。
例如,可以定义一个视图,“列出Physics系在2009年秋季学期所开设的所有课程段”
create view physics_fall_2009 as
select course, course_id, sec_id, building, room_number
from section, course
where course.course_id = section.course_id and
course.dept_name = 'Physics' and
section.semester = 'Fall' and
section.year = '2009';
4.2.2 SQL查询中使用视图
视图可以被当成一个关系来使用,例如,“找出所有在2009年秋季学期在Waston大楼开设的Physics课程”
select course_id
from physics_fall_2009
where building = 'Watson';
4.2.3 物化视图
特定数据库允许存储视图关系,但是它们保证,如果用于定义视图的实际关系被改变,视图也跟着修改,这样的视图被称为物化视图。
4.2.4 视图更新
尽管对查询而言,视图是一个有用的工具,但如果我们用它们来表达更新、插入或删除,它们可能带来严重的问题。困难在于,用视图表达的数据库修改必须被翻译为对数据库逻辑模型中实际关系的修改。所以,视图如果被称为可更新的,需要满足如下限制
- from子句中只有一个数据库关系。
- select子句中只包含关系的属性名,不包含任何表达式、聚集函数和distinct声明。
- 在任何没有出现在select子句中的属性可以取空值。
- 查询部分不包含group by和having子句。
4.3 事务
事务由查询和(或)更新语句的序列组成。SQL标准规定当一条SQL语句被执行时,就隐式地开启了一个事务。下列两条SQL语句之一会结束一个事务。
- commit work。提交当前事务,也就是将该事务所做的更新在数据库中持久保存。在事务被提交后,一个新的事务自动开始。
- rollback work。回滚当前事务,即撤销事务中所有SQL语句对数据库的更新。这样,数据库就恢复到执行该事务第一条语句之前的状态。
如果一个事务还未完成commit work,其影响将被回滚。在执行事务过程中,若发生断点和系统崩溃,在数据库重启时回滚将被自动执行。若事务已经完成commit work,在其影响将不能通过回滚来撤销。
事务的完成是具有原子性的。即一个事务或者在完成所有步骤后提交其行为,或者在不能完成其所有动作的情况下回滚其所有动作。
在很多SQL语句中,默认方式下每个SQL语句自成一个事务,执行完后便自动提交。如果一个事务要完成多个SQL语句,则需要关闭自动提交。
4.4 完整性约束
完整性约束保证授权用户对数据所作的修改不会破坏数据的一致性。
4.4.1 单个关系上的约束
单个关系上的约束包括
- not null。例如,
name varchar(20) not null
- unique。例如,
unique(Aj1, Aj2, ..., Ajn)
,unique
定义的属性元组构成候选码。 - check(<谓词>)。例如,
check(semester in ('Fall', 'Winter', 'Spring', 'Summer'))
。check
子句来保证属性值满足的条件。
上面3条语句是在create table
上加入的。
4.4.2 断言
一个断言就是一个谓词,它表达了我们希望数据库总能满足的一个条件。域约束和参照完整性约束是断言的特殊形式。断言的定义一般如下形式
create assertion <assertion-name> check <predicate>;
注意,还没有一个广泛使用的数据库系统支持断言结构。
4.5 SQL的数据类型与模式
4.5.1 SQL的日期和时间类型
SQL支持日期类型的数据,如下所示
date # 日历日期,包括年、月和日,如'2001-04-25'
time # 一天中的时间,包括时、分和秒,如'09:30:00',可以用time(p)来指定秒的小数点后的数字数,用time with time zone来将时区和时间一起存储
timestamp # date和time的组合,如'2001-04-25 10:29:01.45'。可以用变量timestamp(p)来指定秒的小数点后的数字数,用timestamp with time zone来将时区和时间一起存储
我们可以利用case e as t
形式的表达式来将一个字符串e转换成类型t,其中t是data, time, timestamp
中的一种,而且字符串也必须符合正确的格式。
我们可以利用extract (field from d)
,从date
或time
的值d中提取出单独的域,这里的域可以是year, month, day, hour, minute, second
中的任意一种。时区信息可以用timezone_hour
和timezone_minute
来提取。
还有其他的函数可以获取当前的日期和时间。current_date
返回当前日期,current_time
返回当前时间(带有时区),local_time
返回当前的本地时间(不带时区)。timestamp由current_timestamp
和local_timestamp
返回。
4.5.2 默认值
SQL允许在创建表中使用default
来指定默认值,如下所示
create table student(
...
tot_cred numeric(3, 0) default 0,
...
);
4.5.3 创建索引
索引(index)可以帮助数据库高效地找到关系中那些在索引属性上取给定的元组。例如,如果我们在student关系的属性ID上创建了索引,数据库系统就不用扫描关系中的其他元组,就可以直接找到任何像22201或44553那样具有指定ID值的记录。
常用数据库一般使用如下语句来定义索引
create index studentID_index on student(ID);
上述语句在student关系的属性ID上创建了一个名为studentID_index的索引。
4.5.4 大对象类型
SQL提供字符数据的大对象类型clob
和二进制数据的大对象数据类型blob
,如下所示
book_revoew clob (10kb)
image blob (10MB)
4.5.5 用户定义的类型
SQL标准定义了可使用create type
和create domain
来分别定义类型和域,但这些结构形式还没有被大多数数据库实现完全支持。
4.5.6 create table的扩展
SQL提供了create table like
来支持基于某一个表的结构创建一个新表,如下所示。
create table temp like student;
但上述语句只是创建了一个和student结构完全相同的新表temp,其内容为空。
我们也可以基于查询结果来创建一个新表,如下所示。
create table tl as (
select * from instructor where dept_name = 'music'
)
with data;
如果不加data将不会插入数据。新表的属性是根据查询结果的属性来推导出来的,当然也可以在新表的名称后指定属性名。
4.6 授权
我们可以会限制用户在数据库上的权限,并通过授权的方式来提供权限。权限的授权有两种,对数据的授权(select, update, insert, delete)和对数据库模式的授权(允许用户创建、修改和删除关系)。当用户执行了未被授权的权限时,系统将拒绝执行。
4.6.1 权限的授予与收回
权限的授予使用grant
,如下所示
grant <权限列表>
on <关系名或视图>
to <用户或角色>
例如
grant select on department to Amit, Satashi;
该语句授予了用户Amit和Satashi在department关系上的select权限。
有时我们希望权限可以具体到部分属性,如下所示
grant update(budget)
on department
to Amit, Satashi;
该语句授予了用户Amit和Satashi在department关系上的update权限,但只能update budget。我们也可以在授予insert权限中指定属性,其余未被指定的属性要么被指定为null,要么被指定为default值。
将权限授予给pulic
相当于对当前所有用户和将来用户的授权。
我们可以使用revoke
收回权限,用法语句和grant
时完全相同,如下所示
revoke update(budget)
on department
to Amit, Satashi;
授予用户的权限也可以被用户在授予其他用户,此时权限的收回将会更加复杂。
4.6.2 角色
在SQL中,我们可以将权限授予角色。此时,当新用户创建时,我们直接将角色授予该用户,使得新用户直接获得角色拥有的权限,省去了单独为新用户授权的麻烦。
因此,一个用户或角色拥有的权限包括两部分
- 直接授予的权限。
- 被授予的角色带来的权限。
创建角色使用create role
,如下所示
create role instructor;
然后我们可以将权限授予角色
grant select
on department
to instructor;
然后角色可以被授予到用户或角色,如下所示
grant dean to Amit;
create role dean;
grant instructor to dean;
4.6.5 权限的转移
我们可以在执行grant
命令时附加with grant option
来允许被授权用户将该授权再次授予到其他用户,如下所示。
grant select on department to Amit with grant option;
权限从一个用户到另一个用户的传递可以表示为授权图,但是,相互授权的行为可以导致授权图中存在回路。
用户存在权限的充要条件是存在从授权图的根节点到代表该用户顶点的路径。
4.6.6 权限的收回
当收回一个用户的权限时,其授予其他用户的权限也会被默认级联收回,但可以在revoke
后加上restrict
来防止级联收回。