数据库之中级SQL

本篇文章主要介绍视图,完整性约束、SQL的数据类型和完整性、授权,是笔者学习《数据库系统概念》总结而来,作笔记用。

本篇文章需要用到的关系模式如下:

student(id,name,dept_name,tot_cred);
takes(id,course_id,sec_id,semester,year,grade);

连接表达式

这篇文章中介绍了自然连接,以及join...using子句,它是一种自然连接的形式,用于指定属性上的取值匹配。SQL还支持另外一种形式的连接,其中可以指定任意的连接条件。

on条件允许在参与连接的关系上设置通用的谓词。该谓词的写法与where子句谓词类似,只不过使用的是关键词on而不是where。与using条件一样,on条件出现在连接表达式的末尾。

作为示例,下面语句使用on条件:

SELECT *
FROM student s JOIN takes t ON s.id = t.id;

上述语句表明:如果一个来自student的元组和一个来自takes的元组在属性id上的取值相同,那么它们是匹配的。用自然连接也可以实现相同的查询:

SELECT *
FROM student NATURAL JOIN takes;

这两种查询方式的区别是:使用on条件查询的结果中,id属性会出现两次,一次是student关系中的,另一次是takes关系中的,当然对应的元组上这两个id是相同的;使用自然连接的查询结果中只有一个id属性。使用下面这种方式查询与使用on条件查询结果相同:

SELECT *
FROM student s,takes t
WHERE s.id = t.id

on条件可以表示任何SQL谓词,从而使用on条件的连接表达式就可以表示比自然连接更为丰富的连接条件。目前看来使用on条件的连接表达式的查询与把谓词移到where子句中的查询好像没有区别,事实上引入on条件有两个优点:首先,对于外连接来说on条件的表现与where条件是不同的;其次,如果在on子句中指定连接条件,并在where子句中出现其余的条件,这样的SQL更容易读懂。

外连接

假设我们要显示一个所有学生的列表,显示他们的id、name、dept_name和tot_cred,以及他们所选修的课程。使用自然连接的SQL语句如下:

SELECT *
FROM student NATURAL JOIN takes;

但上述查询与我们想要的结果可能会有些出入。假设有一些学生没有选修任何课程,那么这些学生在student关系中所对应的元组无法与takes关系中的任何元组配对(自然连接要求相同属性上取值相同即匹配成功,这些学生在student关系中对应的id在takes关系中没有)。我们就看不到没有选修任何课程的学生的任何信息。

外连接(outer join)运算通过在结果中创建包含空值元组的方式保留那些在连接中丢失的元组。例如,为了保证没有选修任何课程的学生可以出现在查询结果中,可以在连接结果中加入对应的元组,这些元组在来自student关系的所有属性上的值都被设置为对应的值,在所有余下的来自takes关系属性上的值被设为null。

外连接有三种形式:

  1. 左外连接(left outer join):只保留出现在左外连接运算之前(左边)的关系中的元组。
  2. 右外连接(right outer join):只保留出现在右外连接运算之后(右边)的关系中的元组。
  3. 全外连接(full outer join):保留出现在两个关系中的元组。

为了与外连接运算相区分,之前介绍过的不保留未匹配元组的连接运算被称作内连接(inner join)运算。

我们可以按照如下方式计算左外连接运算:首先,像前面那样计算出内连接的结果;然后,对于在内连接的左侧关系中任意一个与右侧关系中任何元组都不匹配的元组t,向连接结果中加入一个元组r,r的构造如下:

  • 元组r从左侧关系得到的属性被赋为t中的值。
  • r的其他属性被赋为空值。

使用左外连接完成以上查询的语句如下:

SELECT *
FROM student NATURAL LEFT JOIN takes;

假如我们想找出所有一门课程也没有选修的学生,查询语句书写如下:

SELECT *
FROM student NATURAL LEFT OUTER JOIN takes
WHERE course_id IS NULL;

右外连接和左外连接是对称的。来自右侧关系中的不匹配左侧关系任何元组的元组被补上空值,并加入到右外连接的结果中。使用右外连接重写前面的查询并交换列出关系的次序,SQL语句如下:

SELECT *
FROM takes NATURAL RIGHT OUTER JOIN student;

最终得到的结果是一样的,只是属性出现的顺序以takes关系中的属性优先。

全外连接是左外连接与右外连接类型的组合。在内连接结果计算出来之后,左侧关系中不匹配右侧关系任何元组的元组被添上空值并加入到结果中。类似的,右侧关系中不匹配左侧关系任何元组的元组也被添上空值并加到结果中。

on子句可以和外连接一起使用。使用on子句可以达到与“student NATURAL LEFT OUTER JOIN takes”一样的效果,只是属性id在查询结果中出现两次。

SELECT *
FROM student s LEFT JOIN takes t ON s.id = t.id; 

把前述查询中的on子句谓词换成where子句,并把on条件改为true:

SELECT *
FROM student s LEFT OUTER JOIN takes t ON true
WHERE s.id = t.id;

查询结果中没有左侧关系不匹配右侧关系任何元组的元组。原因在于on和where子句在外连接中的表现是不同的。外连接只为那些对相应内连接结果没有贡献的元组补上空值并加入结果。on条件是外连接的一部分,where子句不是(这一句可以结合上面介绍左外连接运算的计算过程理解)。使用where子句的查询中,每个元组都满足连接条件true,因此外连接不会产生出补上空值的元组。外连接实际上产生了两个关系的笛卡尔积,但在takes关系中没有任何元组匹配student关系中的某个元组(这个元组是假设的左侧关系不匹配右侧关系任何元组的元组),因此takes关系中没有与student的这个元组的id取值相同的元组,这样的元组也就被where子句谓词排除了。

连接类型和条件

为了把常规连接和外连接区分开来,SQL中把常规连接称作内连接,连接子句使用inner join。然而关键词inner是可选的,当join子句中没有使用outer前缀,默认的连接类型就是inner join。

下面为所有的连接类型和连接条件表,连接类型和连接条件在使用时可以任意组合。

连接类型
连接类型说明
[inner] join内连接,常规连接,可能会由于两个关系属性不匹配而丢失某些元组
left outer join左外连接,保留出现在左外连接运算左边的关系中的元组。与内连接不同,左侧关系与右侧任何元组不匹配的元组被添加空值后会加到结果中
right outer join右外连接,保留出现在右外连接运算右边的关系中的元组。与内连接不同,右侧关系与左侧任何元组不匹配的元组被添加空值后会加到结果中
full outer join全外连接,是左外连接与右外连接的结合。在内连接结果计算出来之后,左侧关系中不匹配右侧关系任何元组的元组被添加上空值并加到结果中;右侧关系中不匹配左侧关系任何元组的元组被添加上空值并加到结果中。
连接条件
连接条件说明
natural自然连接,匹配条件是两个关系中名称相同的所有属性。
on <predicate>on条件连接,指定连接条件。与外连接组合时是外连接声明的一部分,而与内连接组合时作用与where子句相同。
using(A1,A2,...,An)using条件连接,后跟属性列表,两个关系都必须有列表中的属性,匹配条件是两个关系中的列表的所有属性取值相同。

视图

之前我们一直都在逻辑模型层操作,即我们假定了给定的集合中的关系都是实际存储在数据库中的,但事实上让所有用户都看到整个逻辑模型是不合适的。出于安全考虑,可能需要向用户隐藏特定的数据。

考虑一个职员需要知道教师的标识、姓名和所在系名,但是没有权限看到教师的工资值。此人应该看到的关系由如下SQL语句所描述:

SELECT 教师id,教师名称,系名
FROM 教师关系;

我们可以把上述查询的结果存储下来,然后把存储关系提供给用户。但如果底层数据发生变化(例如教师张三本来是计算机系,某时间转到了软件工程系),那么所存储的查询结果就不再与这些关系上重新执行查询的结果匹配。

SQL允许通过查询来定义“虚关系”,它在概念上包含查询的结果。虚关系并不预先计算并存储,而是在使用虚关系的时候才通过执行查询被计算出来。任何像这种不是逻辑模型的一部分,但作为虚关系对用户可见的关系称为视图(view)。

视图定义

我们在SQL中用create view命令定义视图。为了定义视图,我们必须给视图一个名称,并且必须提供计算视图的查询。create view命令的格式为:

CREATE [OR REPLACE]
[ALGORITHM = {UNDEFINED,MERGE,TEMPTABLE}]
VIEW v[column_list]
AS <query expression>
[WITH [CASCADED | LOCAL] CHECK OPTION]

其中or replace表示如果此视图名已存在则替换,algorithm表示视图select语句使用的算法,有三个值:

  1. UNDEFINED:默认算法,algorithm没指定时选择此算法,表示由数据库系统自动选择使用哪种算法。
  2. MERGE:使用视图时,会把查询语句与定义视图的查询语句合并然后再从定义该视图的关系中查询。
  3. TEMPTABLE:使用视图时,把视图的查询结果存入临时表,然后执行查询语句。

with [cascaded | local] check option表示MySQL会检查更改或插入的元组是否满足视图规则(创建视图时的where子句)。cascaded表示在更新视图时MySQL会根据定义此视图的所有关系的规则检查修改是否合法,例如在关系r上定义了视图v1,在视图v1上又定义了视图v2,在视图v2上又定义了视图v3,定义视图v2时使用了cascaded参数,则更新v2的元组时MySQL会根据关系r、v1、v2的规则检查修改是否合法;local表示MySQL只会检查当前视图的规则检查修改是否合法。v表示视图名,<query expression>可以是任何合法的查询表达式。

重新考虑上面的问题,有些职员不能访问教师的工资值,我们可以把视图关系提供给这些职员,此视图定义如下:

CREATE OR REPLACE ALGORITHM = MERGE 
VIEW city_view(教师id,教师名字,所属系名) AS
SELECT 教师id,教师名字,系名
FROM 教师关系
WITH CASCADED CHECK OPTION;

视图关系在概念上包含查询结果中的元组,但并不进行预计算和存储,数据库系统存储与视图关系相关联的查询表达式。当视图关系被访问时,其中的元组是通过计算查询结果而被创建出来的。也就是说,视图关系是在需要的时候才被创建的。

SQL查询中使用视图

一旦定义了一个视图,我们就可以用视图名指代该视图生成的虚关系。例如使用视图faculty,我们想查询教师名字为“赵彦祖”的教师信息,SQL如下:

SELECT *
FROM faculty
WHERE 教师名字 = '赵彦祖';

可以看到在查询中视图与关系并无差别,事实上,在查询中视图名可以出现在关系名可以出现的任何地方。

直觉上,在任何给定时刻,视图关系中的元组集是该时刻视图定义中得查询表达式的计算结果。因此,如果一个视图关系被计算并存储,一旦用于定义该视图的关系被修改,视图就会过期。为了避免这一点,视图通常这样来实现:当我们定义一个视图时,数据库系统存储视图的定义本身,而不存储定义该视图的查询表达式的执行结果。一旦视图关系出现在查询中,它就被已存储的查询表达式代替。因此,无论我们何时执行这个查询,视图关系都被重新计算。

物化视图

特定数据库系统允许物化存储视图关系,但它们保证:如果用于定义视图的实际关系改变,视图也跟着修改。这样的视图被称为物化视图(materialized view)。MySQL不支持物化视图。

物化视图的结果会存放在数据库中,如果定义该视图的关系被修改,那物化视图的内容也必须更新。保持物化视图一直在最新状态的过程为物化视图维护(materialized view maintenance),简称视图维护(view maintenance)。当构成视图定义的任何关系被更新时,可以马上进行视图维护。有些数据库系统在视图被访问时才执行视图维护,还有一些系统仅采用周期性的物化视图更新方式,在这种情况下,当物化视图被使用时,其中的内容可能是陈旧的。

频繁使用视图的应用将从视图的物化中获益。那些需要快速响应基于大关系上聚集计算的特定查询也会从创建与查询相对应的物化视图中受益。

修改视图

修改视图是指修改数据库中存在的视图,当定义视图的关系某些属性发生变化时,可以通过修改视图来与关系保持一致。

修改视图有两种方式,第一种方式的SQL语句与定义视图相同,只需要在定义视图语句中添加or place即可,在此不再赘述。第二种方式使用alter语句,语法格式如下:

ALTER [ALGOTHM = {UNDEFINED,MERGE,TEMPTABLE}]
VIEW v [column_list]
AS <query expression>
[WITH [CASCADED | LOCAL] CHECK OPTION];

上述修改视图的语句与定义视图的语句只是alter与create的区别。

删除视图

删除视图是指删除数据库中已存在的视图,删除视图时也只是删除数据库中该视图的定义,不会修改定义视图的关系。

删除视图使用drop语句,语法格式如下:

DROP VIEW [IF EXISTS] v;

v表示视图名。drop语句使用if exists时表示如果视图v不存在,执行此删除视图语句不会报错。

视图更新

视图的数据能否支持更新是视情况而定的。当视图支持更新时,对视图的更新事实上是对定义此视图的关系的更新。而对于可更新的视图,视图中的元组和定义该视图的关系的元组之间必须具有一对一的关系。

如果视图定义中包含以下结构,那么它就是不可更新的:

  1. 聚合函数;
  2. DISTINCT;
  3. GROUP BY;
  4. HAVING;
  5. UNION;
  6. 子查询;
  7. JOIN;
  8. ALGORIGHM = TEMPTABLE,使用临时表会使视图变成不可更新的。

从以上可以看出很多时候的视图都是不可更新的,但视图的作用一般也是用于检索而非更新。

事务

事务(transaction)由查询和(或)更新语句的序列组成。SQL标准规定当一条SQL语句被执行,就隐式地开始了一个事务。下列SQL语句之一会结束一个事务:

  1. COMMIT WORK:提交当前事务,也就是将该事务所做的更新在数据库中持久保存,在事务被提交后,一个新的事务自动开始。
  2. ROLLBACK WORK:回滚当前事务,即撤销该事务中的所有SQL语句对数据库的更新。这样,数据库就恢复到第一条语句之前的状态。

关键词work再两条语句中都是可选的。

当在事务执行过程中检测到错误时,事务回滚就起作用了。在某种意义上,事务提交就像对编辑文档的变化存盘,而回滚就像不保存变化退出编辑。一旦某事务执行了COMMIT WORK,它的影响就不能用ROLLBACK WORK来撤销了。数据库保证在发生诸如某条SQL语句错误、断电、系统崩溃这些故障的情况下,如果一个事务还没有完成commit work,其影响将被回滚。

例如A从他的银行账户上转1000元到B的账户上,我们可以先把1000元从A账户扣除然后加到B账户上,如果在把1000元从A账户扣除之后系统崩溃了,这时候B的账户没有发生变化,这1000元就“失踪”了。而回滚就可以防止此类问题,当系统发生崩溃时,在系统重启后1000元又加回A账户。

一个事务或者在完成所有步骤后提交其行为,或者在不能成功完成其所有动作的情况下回滚其所有动作,通过这种方式数据库提供了对事务具有原子性(atomic)的抽象,原子性也就是不可分割性。要么事务的所有影响都反映到数据库中,要么任何影响都没有(在回滚之后)。

完整性约束

完整性约束保证授权用户对数据库所做的修改不会破坏数据的一致性。因此,完整性约束防止的是对数据的意外破坏。

完整性约束通过被看成是数据库模式设计过程的一部分,它作为用于创建关系的create table命令的一部分被声明。然而,完整性约束也可以通过使用alter table table-name [add | modify] constraint命令施加到已有关系上,例如:

//添加唯一约束
ALTER TABLE teacher ADD UNIQUE(name);

//添加非空约束
ALTER TABLE teacher MODIFY name VARCHAR(20) NOT NULL;

其中constraint可以是关系上的任意约束。当执行上述命令时,系统首先保证关系满足指定的约束。如果满足,那么约束被施加到关系上;如果不满足,则拒绝执行上述命令。

not null约束

空值是所有域的成员,因此在默认情况下是SQL中每个属性的合法值。然而对于一些属性来说,空值是不合适的。例如教师表中,教师的id和教师名字为null,这样的元组给出了一个未知的教师信息,因为它不含有有用的信息。

not null声明禁止在该属性上插入空值。任何可能导致向一个声明为not null的属性插入空值的数据库修改都会产生错误信息。其使用方式如下:

CREATE TABLE teacher(
    id int(10) not null,
    name varchar(20) not null
)

类似的,关系模式中的主码上也应该禁止出现空值。但在一个属性被声明为主码时,就意味着它不能为空,因此主码不必显示的声明为not null。

unique约束

SQL还支持下面这种完整性约束:

UNIQUE(A1,A2,...,An)

unique声明指出属性A1,A2,...,An形成了一个超码,即在关系中没有两个元组能在所有列出的属性上取值相同,这些属性可以是一个也可以是多个。如果某属性没有not null约束但有unique约束,则所有元组的该属性值都可以为null,因为空值不等于其他的任何值(包括空值)。

check子句

当应用于关系声明时,check(P)子句指定一个谓词P,关系中的每个元组都必须满足谓词P。

通常用check子句来保证属性值满足指定的条件,实际上创建了一个强大的类型系统。例如:

CREATE TABLE student(
    id INT(10) PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    age INT(2) CHECK(age BETWEEN 14 AND 18),
    gender VARCHAR(10) NOT NULL,
    CHECK(gender IN ('男','女'))
)

其中age属性后的check子句保证了该关系中所有元组的age属性值在14和18之间,如果插入小于14或者大于18的age属性的元组会报错。类似的,create table语句中最后一行的check子句指定了gender的值只能为“男”或“女”。需要注意的是,MySQL中的check子句会被忽略,虽然语法上是没有问题的(为了提高兼容性),即check在MySQL中只是一个没有作用的关键字。

参照完整性

我们常常希望保证在一个关系中给定属性集上的取值也在另一关系的特定属性集的取值中出现,这种情况称为参照完整性(referential integrity)。

更一般的,令关系r1和r2的属性集分别为R1和R2,主码分别为K1和K2。如果要求对r2中任意元组t2,均存在r1中元组t1使得t1.k1=t2.α,我们称R2的子集α为参照关系r1中k1的外码(foreighn key)。这种要求称为参照完整性约束(referential-intergrity constraint)或子集依赖(subset dependency)。后种称法是由于上述参照完整性可以表示为这样一种要求:r2中α上的取值集合必须是r1中k1上的取值集合的子集。不同于外码约束,参照完整性约束通常不要求k1是r1的主码,其结果是,r1中可能有不止一个元组在属性k1上取值相同。

外码可以用作为SQL中create table语句一部分的foreign key子句来声明。默认情况下,SQL中外码参照的是被参照关系中的主码属性。例如:

CREATE TABLE student(
    id INT(10) PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    age INT(5) CHECK(age BETWEEN 14 AND 18),
    gender VARCHAR(10) NOT NULL,
    teacher_id int(10) not null,
    FOREIGN KEY(teacher_id) REFERENCES teacher(id)
        ON DELETE [CASCADE | NO ACTION | SET NULL | RESTRICT]
        ON UPDATE [CASCADE | NO ACTION | SET NULL | RESTRICT],
    CHECK(gender IN ('男','女'))
)

上述语句中将teacher_id属性设置为外码,参照的属性是teacher关系中的id属性。而on delete和on update子句分别定义了当teacher关系中的id属性被删除或者更新时student关系的teacher_id属性的行为,其取值如下:

  1. cascade:当删除或更新参照关系中的对应元组时,先检查该记录是否有对应的外键,如果有则删除或更新参照了被删除或更新外键的元组。
  2. set null:当删除参照关系中的对应元组时,删除参照了被删除外键的元组;当更新参照关系中的对应元组时,设置参照了被更新外键的元组的属性值null(前提是该属性值没有not null约束)。
  3. no action或restrict:当删除或更新参照关系中的对应元组时,先检查该记录是否有对应的外键,如果有则不允许删除或更新。

SQL的数据类型和模式

MySQL中的日期和时间类型

MySQL的日期和时间类型如下表:

日期和时间类型占用空间(字节)最小值最大值零值说明
DATETIME81000-01-01 00:00:009999-12-31 23:59:590000-00-00 00:00:00表示年月日时分秒,DATE和TIME的组合,格式:YYYY-MM-DD HH:MM:SS
DATE41000-01-01 9999-12-31 0000-00-00表示年月日,格式:YYYY-MM-DD
TIME3-838:59:59838:59:5900:00:00表示时分秒,但为表示特殊时间间隔,小时的范围扩大且支持负值,格式:HH:MM:SS
YEAR1190121550000表示年份,格式:YYYY
TIMESTAMP4197001010800012038 年的某个时刻00000000000000表示年月日时分秒,与DATETIME相比,存储范围较小。但与时区相关,插入时先转换为本地时区再存储,查询时先转换为本地时区再显示。格式:YYYY-MM-DD HH:MM:SS

MySQL还定义了一些函数以获取当前日期和时间。

  1. now():获取系统日期,格式:YYYY-MM-DD HH:MM:SS,返回的是SQL语句开始执行的时间。
  2. sysdate():获取系统日期,格式:YYYY-MM-DD HH:MM:SS,返回的是动态时间。
  3. curdate():获取系统年月日,格式:YYYY-MM-DD。
  4. curtime():获取系统时间,格式:HH:MM:SS。
  5. year(date):获取给定日期的年份。
  6. month(date):获取给定日期的月份。
  7. current_timestamp():获取当前时区的日期,格式:YYYY-MM-DD HH:MM:SS。
  8. localtime():获取当前日期,格式:YYYY-MM-DD HH:MM:SS。

等等一系列函数,还有很多在此不再列举了,等到使用时再看MySQL有没有支持对应的功能的函数。

默认值

SQL允许为使用default属性指定默认值,如下所示:

CREATE TABLE default_test(
	id INT(10) NOT NULL,
	name VARCHAR(20) NOT NULL DEFAULT '赵彦祖'
)

属性name的默认值被声明为“赵彦祖”,当插入一个元组时,即便name属性有not null约束,此元组也可以不给出name属性的值。例如以下插入语句:

INSERT INTO default_test(id) VALUES(1);

可以执行成功,且存储在数据库中的是id属性值为1,name属性值为“赵彦祖”的元组。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值