前面各节介绍了关系代数、关系设计理论、高层概念表示,本节之后将介绍SQL语言。
目前主流的数据库如MySQL、Oracle等只是实现了SQL标准的部分,而且各有扩展,具体的细节需要查询各个数据库的文档。本节只是作为SQL的入门来进行介绍。
MySQL文档库:http://dev.mysql.com/doc/
Oracle的文档库:http://www.oracle.com/technetwork/cn/indexes/documentation/index.html
6 SQL查询语言
6.1 简单查询
典型的查询为SELECT-FROM-WHERE形式,FROM子句给出查询所引用的关系,WHERE子句用来给出选择条件,SELECT决定哪些属性应该被显示出来。
先给出一个示例:SELECT title AS name, length AS duration FROM Movie WHERE studioName='Disney' or studioName like '%ney%' order by duration;
从关系代数的角度,SELECT用来投影关系的字段,这个字段可以使用AS来进行重命名。基于扩展也可以将常量表达式或属性的表达式进行投影。
使用ORDER BY <attr,...>来进行结果的排序,默认为ASC,即升序,也可以在后面加DESC来降序排列。ORDER BY位于任何语句之后,执行时在FROM和WHERE的结果上进行排序,排序之后再应用SELECT。ORDER BY后的列表,也可以包含表达式。
WHERE子句来实现选择操作,条件可以通过逻辑运算符AND,OR,NOT来组合,分别为并、或和非运算。
此外SQL提供了模式匹配用于字符串比较。使用方式为 like 'pattern',模式为一个可能使用%或_的字符串,%可以匹配源中任意长度的字符串_可以匹配任意一个字符。
SQL允许属性有一个特殊值NULL。在WHERE子句中,当对NULL进行运算时有两个重要的规则:第一,NULL和任何值进行算术运算,其结果仍为NULL。第二,当使用比较运算符时,NULL和任意值比较,结果均为UNKONWN。如果希望断定某属性是否为NULL,则需要使用关键字IS NULL或IS NOT NULL。
上面介绍到NULL的比较会产生UNKONWN的BOOL值,与通常的TRUE/FALSE并列。我们可以将TURE视为1,FALSE视为0,而UNKONWN视为0.5,则有如一规则:
第一:两个布尔值之间AND运算取两者最小的值。例如存在FALSE的比较结果为FALSE,而TRUE和UNKONWN的比较为UNKONWN。
第二:两个布尔值之间OR运算取两者最大的值。
第三:布尔值x的非为1-x。即TRUN和FALSE可以互非,而UNKNOWN的非是其自身。
6.2 多关系查询
SQL用简单的方式在一个查询中处理多个关系,即在FROM子句中列出每个关系,然后在SELECT和WHERE子句中引用任何出现在FROM子句中关系的属性。
示例:SELECT name FROM Movie as m ,MoveExec as e WHERE m.title='star war' AND m.produceNo = e.certNo
当查询涉及多个关系的时候,关系中可能会有属性重名。此时需要以明确的方式来指明我们选择的是哪个关系上的属性。SQL通过在属性前面加上关系名和一个点.来解决这个问题。
关系代数中可以用集合操作来组合关系,SQL中对应的操作作用在查询结果上。关键字UNION, INTERSECT,EXCEPT分别对应U,M,-。当这此关键字用于两个查询时,查询应该分别用括号括起来。
6.3 子查询
当某个查询是另一个查询的一部分时,称其为子查询。子查询还可以拥有下一级子查询,如此递推。前面介绍了通过使用集合操作合并两个子查询的结果。下面介绍三种新的子查询的形式。
6.3.1 子查询返回单个值用于WHERE子句
可以将此类查询用于上层查询的WHERE子句中。
例如SELECT name FROM MoveExec WHERE certNo = (SELECT produceNo FROM Move WHERE title = 'star war');
6.3.2 返回关系的子查询用于WHERE子句
SQL定义了如下几个运算符,作用于关系,可以返回BOOL结果。
s IN R 如果s存在于R中
EXIST R 关系R非空
s > ALL R 表示 s >max(R)
s > ANY R 表示s > min(R)
基于上面的定义,如果一个元组和一个关系有相同的属性个数,则可以使用上面的运算符来组合子查询。例如:
SELECT producerNo FROM Movies WHERE (title,year) IN ( SELECT mtitle ,myear FROM StarsIn WHERE startName = 'Harrison Ford');
上面介绍的子查询只需要计算一次,返回结果给上层查询。更复杂的查询要求子查询计算多次,每次需要从上层传递外部元组变量给子查询,称此类查询为关联子查询。
SELECT title FORM Movies AS old WHERE year < ANY ( SELECT year FROM Movies WHERE title = old.title);
6.3.3 返回关系的子查询用于FROM子句
返回的关系需要起个名字以便用于FROM子句中,例如:SELECT title FROM Movies, (SELECT * from Movies) AS old where old.title =title and old.year < year;
6.3.4 SQL的连接表达
本节介绍的连接运算符是冗余的,它可以用SELECT FROM WHERE表达式替换。最简单的连接为笛卡儿积,但实际上较少使用,形式为R CROSS JOIN S。CORSS可以省略。
θ连接形式:R JOIN S ON <条件>
示例:SELECT * FROM Movies JOIN StarsIn ON title = mTitle and year = mYear;
6.3.5 θ外连接
形式:R [ LEFT|RIGHT ] OUTER JOIN S ON <条件>
6.3.6 自然连接
自然连接形式:R NATURAL JOIN S
由于自然连接自行为在同名且值相等的属性上连接,因此不需要外加条件。因此上面的示例在两个表的title和year字段同名时,可以化简:
SELECT * FROM Movie NATURAL JOIN StarsIn .
MySQL使用INNER JOIN 来表示自然连接,但可以通过 using 来指定使用的列。此外MySQL在内连接时会自行判断载入表的先后顺序,用户也可指定载入顺序,这需要使用MySQL的扩展连接关键字STRAIGHT_JOIN。其语法为 R STRAIGHT_JOIN S on
6.3.7 外自然连接
外自然连接形式:R NATURAL [ LEFT | RIGHT ] OUTER JOIN S
示例:SELECT * FROM Movies NATURAL LEFT JOIN StarsIn;
6.4 全关系操作
6.4.1 消除重复
如果希望结果中不出现重复元组,可以使用SELECT DISTINCT。然而消除重复的代价较高,只有在排序或分组才能保证相同的元组紧挨在一起。
与SELECT不同的是,UNION等集合操作,默认是不消除重复的。如果希望在集合运算中保留重复,需要在集合运算符后加ALL关键字。
我们也可以从 聚集操作符中消息重复,方法是在聚集运算符内对列加DISTINCT,这样可以在聚集操作符之前消除重复。
6.4.2 分组
分组使用group by可以对列进行分组和聚集。当元组中含有空值时要注意如下规则:
空值在任何聚集操作中都被忽略;除了计数以外,对空包执行聚集操作,结果均为NULL。空包的计数结果为0。
6.4.3 HAVING子句
如果需要对基于某些分组聚集的性质选择分组,可以在GOURP BY后面加上HAVING子句,HAVING子句后跟一个分组的条件。表示分组必须满足所给的条件,才可以作为结果返回。
示例,列出在2000之前制作过电影的制片人所摄制的电影个数
SELECT name,COUNT(title) FROM movie,movieexec WHERE movie.`producerC#` = movieexec.`cert#`GROUP BY name HAVING MIN(year)<2000;
6.5 数据库更新
6.5.1 元组插入
INSERT INTO R(a1,a2,...an) VALUES(v1,v2,...vn),属性列表可以省略,此时必须保证属性值的顺序与关系属性的标准顺序一致。可以通过子查询向关系中插入计算出的元组集合。
例如:INSERT INTO Movies VALUES ('star war', 1980, 120, 'POP')。
6.5.2 删除
DELETE FROM R WHERE <条件>,如果没有条件,则所的元组都会被删除。
6.5.3 更新
UPDATE R SET <col = val,...> WHERE <条件>。
6.6 事务
先介绍串行化的概念,为了支持事务的并行性,需要事务都是可串行化,即这些事务执行起来必须表现的串行执行的,没有重叠并行。
除了串行化,另一概念是原子性,即事务要么执行成功,要么没有执行,不存在执行一部分的问题。
6.6.1 SQL中提交事务
SQL支持事务,使用START TRANSACTION标识事务开始,使用COMMIT提交事务或使用ROLLBACK回滚事务。
通常多个读事务可以并发执行,但涉及到写事务则需要考虑具体情况。SQL允许告诉系统下面要执行的事务是性质为只读还是读写的。
SET TRANSACTION READ [ ONLY | WRITE ]
6.6.2 脏读与隔离级别
脏数据指还没提交的事务所写的数据的通用术语。脏读是对脏数据的读取。脏读的风险是写数据的事务可能回失败,那么读取的数据就是无效的。脏读有时是重要的,有时脏读是无关紧要的,脏读可以提高性能。
SQL设备了四种隔离层次,通过设置事务的隔离层次,可以让我们控制如何处理脏数据。
隔离解别 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted | Y | Y | Y |
Read Committed | N | Y | Y |
Repeatable Read (default) | N | N | Y |
Serializable | N | N | N |
隔离层次对事务进行设置。如SET TRANSACTION ISOLATION LEVEL READ COMMITED。
对于读提交层次,SQL允许同一个查询执行多次并获得不同的结果。只要结果反应了已经提交事务定写入的数据。
对于可重复读层次,如果第一次检索到一个元组,则后续重复这个查询会再次检索到同一元组。然而,后续操作会可能会遇到幻象元组。例如其他事务插入新元组,而本事务也想插入同一新元组,此时就冲突了,幻象元组突然“出现”起作用了。
在串行话层次上,事务必须等待前一个执行结束方能执行。
7 约束与触发器
7.1 键与外键约束
7.1 外键的创建
我们在第二节已经看到主键的两种定义方式,外键与之类似,也有两种定义方式:
在单个属性上的外键:在属性声明之后添加REFERENCE <TABLE>(<ATTR>)
如果外键为多个属性,则必须在CREATE TABLE语句的属性列表之后追加声明 FOREIGN KEY(<ATTR LIST>) REFERENCE <TALBE> (<TABLE ATTR LIST>)
示例:CREATE TABLE Studio (name CHAR(20) PRIMARY KEY,presC# INT, FOREIGN KEY (presC#) REFERENCE MovieExec(cert#) ) ;
7.1.2 维护引用完整性
外键约束有当违反时有三种不同的处理方式:
第一,缺省的拒绝违法操作;
第二,级联(CASCADE)原则,被引用属性的改变被应用到外键上。
第三,置空NULL,被引用的关系上的更新影响外键时,外键被置为空值。
这三个选项可以独立的选择删除和修改,同外键一起声明。声明方法为在ON DELETE 或ON UPDATE 后加SET NULL 或 CASCADE选项。
示例:CREATE TABLE Studio (name CHAR(20) PRIMARY KEY,presC# INT, FOREIGN KEY (presC#) REFERENCE MovieExec(cert#) ON DELETE SET NULL ON UPDATE SET CASCADE);
7.1.3 延迟约束的检查
7.2 属性与元组约束
7.2.1 非空约束
CREATE TABLE中与属性相连的简单约束为NOT NULL,其作用为不允许元组的此属性取NULL。
7.2.2 基于属性的CHECK约束
更复杂的约束是将保留字CHECK和圆括号括起来的条件附加在属性声明上,此条件为此属性的每个值都必须满足的条件。原则上CHECK条件可以是任何在WHERE了句中允许的描述。CHECK约束是在元组在此属性上获得新值时被检查,如果此属性没有变化,则不会执行检查。
示例:CREATE TABLE ....., gender CHAR(1) CHECK (gender IN ('F','M') )
7.2.3 基于元组的CHECK约束
如果CHECK涉及多个属性,则需要在CREATE TABLE的属性定义之后声明CHECK,这点类似于键约束。
示例:在添加影星时,如果为男性,则不能以小姐字样开头
CREATE TABLE MovieStar (name CHAR(30) PRIMARY KEY, gender CHAR(1), CHECK (gender IN ('F','M') OR name not like 'Ms.%' )
7.3 约束修改
7.3.1 约束命名
在约束前添加CONSTAINT 和约束的名字即可。
对于主键:name char(30) CONSTRAINT name_pk PRIMARY KEY
对于约束: gender char(1) CONSTAINT no_mr CHECK (gender IN ('F','M'))
7.3.2 约束的修改
约束在命名之后,可以根据名字来修改。
约束删除:ALTER TABLE tbl_name DROP CONSTRAINT cons_name;
约束添加:ALTER TABLE tbl_name ADD CONSTRAINT cons_name CHECK/PRIMARY KEY ()/FOREIGN KEY()
7.4 断言
CREATE ASSERTION <name> CHECK (<condition>)
DROP ASSERTION name
MySQL并不支持断言,不在此详细介绍。
7.5 触发器
8 视图与索引
通常我们所说的视图即虚拟视频,其并不进行存储,但可以对其查询,就好像其确实存在于数据库中一样。视图可以被物化,需要定期地进行构造并存储。索引是一种物化视图。
8.1 虚拟视图
SQL中视图的定义方式为CREATE VIEW <viewname> AS <SELECT 语句>,即视图其实是对一次查询结果的虚拟存储,方便后续使用。视图在定义之后,和真实的关系表一样,可以在FROM、WHERE子句中使用。在定义视图时还可以重命名属性,方法为在视图名后加一对圆括号,将视图的属性定在括号内。
示例:CREATE VIEW DisneyMovie ( film, year ) AS SELECT title,year FROM Movies Where studioName = ‘disney’;
删除视图:DROP VIEW viewname; 删除视图不影响原始表的数据。
SQL中规定对于一些简单的视图,可以对视图进行插入、删除和更新,我们不在此进行描述。
8.2 SQL索引及选择
8.2.1 索引的创建和删除
关系中属性的索引是一种数据结构,可以提高对应属性上查询的效率,常用的实现为二叉查找树,键为属性的可能的值,键对应的值为相应分组的存储位置。索引可以极大的提高相应列上查找的速度,如果没有索引,我们不得不遍历所有的元组从中过滤出符合条件的部分。
索引创建语法:CREATE INDEX idx_name ON R(column,...),在关系R的相应列上创建一个索引,可以针对多个属性作联合索引,此时要注意属性列表的顺序选择。
索引删除:DROP INDEX idx_name
除了上面介绍的索引创建方式,另一种常用的索引创建方式是在创建表时,将相应的列声明为KEY,这样DBMS会默认为相应列创建索引。
8.2.2 索引的选择
索引不但可以加快查找,在相应关系连接时,也可以加快操作,但索引的维护是有代价的,当相应列有新元组插入、删除和修改时,都需要更新索引。
关系上最有用的索引为主键创建;其次,如果某些属性虽然不是键,但几乎可以作键用,则也可以创建索引;最后,如果元组在某些属性上是聚集的,则可以为此属性添加索引。
为了计算最佳索引,需要统计查询和更新在基于关系的哪些属性上最为频繁的发生。当对频繁度有排序后,按从高到低依次添加索引,会为我们带来最大的效率。
8.3 物化视图
SQL标准支持物化视图,但在MySQL中并不是天然支持的。
物化视图创建:CREATE MATERIALIZED VIEW viewname AS <SELECT .....>
物化视图的更新不需要在基本表改变时进行,可以离线的在数据库不繁忙的时候执行。
视图和物化视图可以简化查询语句。
9 存储过程
10 权限管理
授权:GRANT <权限列表> ON <DB ELEMENT> TO <USER> [WITH GRANT OPTION]
收权:REVORK <权限列表> ON <DB ELEMENT> FROM <USER> [ CASCADE| RESTRICT ]
DBMS使用授权图来跟踪所授予出到的权限。
参考文献
MySQL隔离级别 http://blog.csdn.net/taylor_tao/article/details/7063639