- 分布式数据服务:事务模型、处理语言、一致性与体系结构
- 徐子晨 柳杰 娄俊升
- 12字
- 2024-06-27 16:35:08
第2章 分布式数据处理语言
2.1 SQL
SQL最早的版本是由IBM开发的,最初被叫作Sequel,从20世纪70年代一直发展至今,其名称已变为SQL。SQL全称为Structured Query Language(结构化查询语言),是一种专门用来与数据库沟通的语言,它利用一些简单的句子构成基本的语法来存取数据库的内容。
SQL于1986年成为ANSI标准。每隔几年,该标准就会更新一次,ANSI SQL-89和ANSI SQL-92分别是1989年和1992年添加的不同SQL标准集。即使有了这些标准,也没有两个关系数据库管理系统是完全相同的。它们都只是部分符合ANSI。所以读者应该意识到不同SQL实现之间的差异。大体而言,业界的产品都是在包含ANSI SQL的基础下,再扩充自家产品的功能,以求能展现产品本身的特色。
如果你只在一个RDBMS中工作,那么不遵循ANSI标准是完全可以的。但当你的代码在一个RDBMS中工作,而你想在另一个RDBMS中使用该代码时可能会出现问题。没有遵循ANSI标准的代码可能无法在新的RDBMS中运行。
尽管SQL被称为查询语言,但其功能包括数据查询、数据定义、数据操纵和数据控制。SQL简洁方便,功能齐全,目前它已经成为关系数据库系统中使用最广泛的语言。SQL不是某个特定数据库供应商专有的语言,几乎所有重要的DBMS都支持SQL。SQL广泛应用于各种大、中型数据库,如Sybase、SQL Server、Oracle、DB2、MySQL、PostgreSQL等;也用于各种小型数据库,如FoxPro、Access、SQLite等。
SQL主要有以下分类。
●数据定义语言(Data Definition Language,DDL):主要用于创建、修改和删除数据库对象(数据表、视图、索引等),包括CREATE、ALTER、DROP语句。
●数据查询语言(Data Query Language,DQL):主要用于查询数据库中的数据,其主要语句为SELECT语句。SELECT语句是SQL语言中最重要的部分。SELECT语句中主要包括5个子句,分别是FROM子句、WHERE子句、GROUP BY子句、HAVING子句和WITH子句。
●数据操纵语言(Data Manipulation Language,DML):主要用于更新数据库里数据表中的数据,包括INSERT(插入)语句、UPDATE(更新)语句、DELETE(删除)语句。
●数据控制语言(Data Control Language,DCL):主要用于授予和回收访问数据库的某种权限,包括GRANT、REVOKE等语句。其中,GRANT语句用于向用户授予权限,REVOKE语句用于向用户收回权限。
●事务控制语言(Transaction Control Language,TCL):主要用于数据库对事务的控制,保证数据库中数据的一致性,包括COMMIT、ROLLBACK等语句。其中,COMMIT用于事务的提交,ROLLBACK用于事务的回滚。
本章主要介绍SQL的使用和主要功能,以及各种连接的表达、视图、事务、完整性约束、授权等内容。通过本章的学习,读者应了解SQL的特点,掌握SQL的四大功能及使用方法,重点掌握数据查询功能,并加深对数据库管理系统中数据查询、数据定义、数据操纵和数据控制功能实现原理的理解。
2.1.1 SQL基础
SQL的基本概念如下。
●基本表。基本表是独立存在的表,一个关系对应一个基本表,一个或多个基本表对应一个存储文件。表中的一行代表一种联系。在关系模型的术语中,关系用来表示表,而元组用来表示表中的行,属性用来表示表中的列。
●视图。视图是从一个或几个基本表导出的表,是一个虚表。数据库中只存放视图的定义而不存放视图对应的数据,这些数据仍存放在导出视图的基本表中。当基本表中的数据发生变化时,从视图查询出来的数据也随之改变。在用户看来,视图是通过不同路径去看一个实际表,透过视图可以看到数据库中用户感兴趣的内容。SQL支持的关系数据库的三级模式结构,如图2-1所示。其中,外模式对应于视图和部分基本表,模式对应于基本表,内模式对应于存储文件。
图2-1 SQL支持的关系数据库的三级模式结构
●事务。SQL标准规定当一条SQL语句被执行后,就会隐式地开始一个事务。一个事务在完成所有步骤后提交,或者在不能成功完成所有步骤的情况下回滚其所有动作,通过这种方式数据库提供了事务具有原子性的特性,原子性也就是不可分割性。要么事务被完整地提交到数据库中,要么什么都不提交。
●完整性约束。完整性约束保证用户对数据库所做的修改不会破坏数据的一致性。因此,完整性约束防止对数据的意外破坏。一般说来,一个完整性约束可以是属于数据库的任意谓词。完整性约束通常被看成是数据库模式设计过程的一部分,它作为用于创建关系的CREATE table命令的一部分被声明。然而,完整性约束也可以通过使用ALTER table table-name ADD constraint命令施加到已有关系上,其中constraint可以是关系上的任意约束。当执行上述命令时,系统首先要保证关系满足指定的约束。如果满足,那么约束被施加到关系上;如果不满足,则拒绝执行上述命令。
●授权。我们可能会在数据库的某些部分给一个用户授予几种权限。对数据的授权包括:
○授权读取数据。
○授权插入新数据。
○授权更新数据。
○授权删除数据。
每种类型的授权都被称为一个权限。我们可以在数据库的某些特定部分(如一个关系或视图)上授予用户这些类型的权限。当用户执行SQL语句时,SQL先基于该用户曾获得的权限检查此查询或更新是否是授权过的。如果查询或更新没有经过授权,那么该SQL语句将被拒绝执行。除了在数据上的授权之外,用户还可以被授予数据库模式上的权限。例如,可以允许用户创建、修改或删除关系。拥有某些权限的用户还可以把这样的权限给其他用户或者撤销授出的权限。
SQL标准支持如下多种固有数据类型。
●char(n):固定长度的字符串,用户指定长度n。
●varchar(n):可变长度的字符串,用户指定最大长度n。
●int:整数类型。
●smallint:小整数类型。
●numeric(p,d):定点数,精度由用户指定。这个数有p位数字(加上一个符号位),其中d位数字在小数点右边。
●real,double precision:浮点数与双精度浮点数,精度与机器相关。
●float(n):精度至少为n位的浮点数。
char数据类型存放固定长度的字符串。例如,属性A的类型是char(5)。当存入字符串“ABC”时,该字符串后会追加2个空格使其达到5个字符的串长度。反之,如果属性B的类型是varchar(5),在属性B中存入字符串“ABC”,则不会增加空格。当比较两个char类型的值时,如果它们的长度不同,在比较之前会自动在较短的字符后面加上额外的空格使它们的长度一致,但这主要取决于数据库系统,有的数据库系统并不会有对齐操作。所以上述属性A和B中存放的是相同的值“ABC”,A=B的比较也可能返回假。我们可以使用varchar类型而不是char类型来避免这样的问题。
SQL由一些简单的子句构成,所有的SQL语句均有自己的格式。下面是一段SQL代码,查询每位员工在2021年完成的销售数量,查询语句结构如图2-2所示。
图2-2 SQL语句结构
●语句:语句以关键字开头,分号结尾。如果语句以关键字SELECT开头,则整个语句被称为SELECT语句。
●子句:子句是语句和查询的组成成分,我们一般使用的子句以SELECT、FROM、WHERE、GROUP BY、HAVING和ORDER BY开头。
●标识符:标识符是数据库对象的名称,例如表或列的字段名。尽管SQL语言不区分大小写,为了可读性,标识符通常是小写的,关键字通常是大写的。
●函数:函数是一种特殊类型的关键字。它接受零个或多个输入,对输入进行处理,返回一个输出。SQL支持很多类型的函数,例如聚合函数、数学函数、字符串函数、时间日期函数等。常见的函数如表2-1所示。
●表达式:可以将表达式看作一个产生值的公式。图2-2代码块中的表达式为:COUNT(s.sale_id)。此表达式包含一个函数COUNT和一个标识符s.sale_id,它们一起形成一个表达式,表示要计算销售额。
●别名:别名仅在查询期间临时重命名列或表。换言之,新别名将显示在查询结果中,但原始的列名或者表名在要查询的表中保持不变。标准是在重命名列时使用AS,例如图2-2代码块中使用COUNT(s.sale_id) AS num_sales,而在重命名表时用空格隔开,例如employee e。但在实际运用中,这两种语法都适用于列和表,即以上两种方法都可以用于列和表。
●谓词:谓词是一种逻辑比较,它会产生以下三个值之一:TRUE、FALSE、UNKNOWN。它们有时被称为条件陈述。谓词用于WHERE子句和HAVING子句的搜索条件中,还用于 FROM 子句的连接条件以及需要布尔值的其他构造中。
●注释:注释是代码运行时被忽略的文本。在代码中写注释很有用,这样其他代码的审阅者可以快速理解代码的意图,而无须阅读所有代码。
○单行注释示例:- -这些是我的注释
○多行注释示例:/*这些是
我的注释*/
●引号:在SQL中可以使用两种类型的引号,即单引号和双引号。引用字符串值时使用单引号,引用标识符时使用双引号。在实际使用过程中,与双引号相比,单引号要多得多。
表2-1 常用函数
2.1.2 SQL的查询语句
要编写SQL语句,需要深入地理解基本的数据库设计。要想编写高效的SQL就必须要知道信息存放在哪些表中,表与表之间如何互相关联。为帮助你更好地学习和理解本节的相关知识,图2-3显示了一个包含三张表的简单数据库,分别是学生表(Students table)、课程表(Courses table)和选课信息表(Elecourses table)。由图2-3可知这三张表是如何定义的以及它们如何相互连接的。本节的所有例子都基于这三张表。
图2-3 简单数据模型图
SELECT语句被称为查询语句,查询语句由6个主要子句组成,分别是SELECT子句、FROM子句、WHERE子句、GROUP BY子句、HAVING子句、ORDER BY子句。我们接下来详细介绍每一条子句,最后介绍由MySQL、PostgreSQL和SQLite支持的LIMIT子句,并通过例子介绍SQL的详细语法。
SELECT查询分为单关系查询和多关系查询。单关系查询是指从一张表中查询需要的信息,而当需要的信息存放在多张表中,要从几张表的组合中得到我们想要的信息时执行的就是多关系查询。
下面开始介绍单关系查询。查询数据库意味着从数据库的一个表或多个表中检索数据。当只在一张表中查询信息时,所使用到的就是单关系查询。在SELECT子句中,SELECT关键字后面可以是列名和表达式,这些列名和表达式用逗号分隔。
我们考虑使用Students表做一个简单的查询:“查询所有学生的学号和姓名”。下述查询语句利用SELECT语句从Students表中检索名为stu_id和stu_name的列。学生的学号和姓名可以在Students关系中找到,因此我们将该关系放到FROM子句中。学生的学号和姓名在属性stu_id和stu_name中,我们把这些属性写到SELECT子句中,如程序清单2.1所示。总的来说,SELECT语句的基本语法为:所需的列名写在SELECT关键字之后,FROM关键字指出从哪个表中检索数据。
程序清单2.1 SELECT检索多列
除了指定所需的列(如上所述,一个或多个列)外,SELECT语句还可以检索所有的列而不必逐个列出它们。在实际列名的位置使用星号(*)通配符可以做到这点,如程序清单2.2所示。
程序清单2.2 SELECT检索所有列
如前所述,SELECT语句返回所有匹配的行。但是,如果你不希望查找出来的相同值每次都出现,该怎么办呢?办法就是使用DISTINCT关键字,顾名思义,它指示数据库只返回不同的值。例如,你想检索Students表中所有学生的籍贯(origin),如程序清单2.3所示。
程序清单2.3 SELECT返回不同的行
SQL允许在 WHERE子句中使用逻辑连词 AND、OR 和 NOT。逻辑连词的运算对象可以是包含比较运算符<、<=、>、>=、=和<>的表达式。SQL允许我们使用比较运算符来比较字符串、算术表达式以及特殊类型,如日期类型。在本节的后面,我们还将研究WHERE子句谓词的其他特征。
至此,我们已经学习了如何使用SQL的SELECT语句来检索单个表列、多个表列以及所有表列,也学习了如何返回不同的值。接下来开始介绍多关系查询。
之前的查询都是基于单个关系的,但当我们需要从几张表中组合信息时,如何书写这样的查询?答案是使用多关系查询。
考虑查询“对于所有学生,找出他们的学号姓名以及所选课程标识”,我们需要把访问到的关系写在FROM子句当中,在WHERE子句中指定匹配的条件,如程序清单2.4所示。
程序清单2.4 多关系查询
如前所述,一个SQL查询包含三种类型的子句:SELECT子句、FROM子句、WHERE子句。一个典型的SQL查询具有如下的形式:
SELECT A1,A2,…,An
FROM r1,r2,…,rm
WHERE P;
其中Ai代表一个属性,ri代表一个关系,P代表一个谓词。
我们接着讲授如何使用SELECT语句的ORDER BY子句根据需要排序检索出的数据。
前面的SQL语句返回某个数据库表的单个列,但输出并没有特定的顺序。检索出的数据并不是随机显示的。如果不排序,数据一般将以它在底层表中出现的顺序显示,这有可能是数据最初被添加到表中的顺序。为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。ORDER BY子句取一个或多个列的名字,据此对输出进行排序,如程序清单2.5所示。
程序清单2.5 ORDER BY升序排序
数据排序不限于升序排序(从A到Z),这只是默认的排序顺序。还可以使用ORDER BY子句进行降序(从Z到A)排序。为了进行降序排序,必须指定DESC关键字,例如ORDER BY stu_name DESC。
DESC关键字只应用到直接位于其前面的列名。在ORDER BY子句中,对stu_name列指定DESC。因此,stu_name列以降序排序。如果想在多个列上进行降序排序,必须对每一列指定DESC关键字。请注意,与DESC相对的是ASC,在升序排序时可以指定它。如果既不指定ASC也不指定DESC,则默认为ASC,也就是ORDER BY默认为升序。
需要注意的是ORDER BY子句必须是SELECT语句中的最后一条子句。我们可以根据需要利用ORDER BY在一个或多个列上对数据进行排序。
接下来将介绍如何使用SELECT语句的WHERE子句指定搜索条件。在SELECT语句中,数据根据WHERE子句中指定的条件进行过滤,返回用户需要的行。
SQL支持表2-2列出的所有条件操作符。表2-2中列出的某些操作符是冗余的(如<>与!=相同,!<相当于>=)。并非所有DBMS都支持这些操作符。要确定你的DBMS支持哪些操作符,请参阅相应的文档。
所有WHERE子句在过滤数据时使用的都是单一的条件。为了进行更强的过滤控制,SQL允许给出多个WHERE子句。这些子句有两种使用方式,即AND子句或OR子句的方式。要通过不止一个列进行过滤,可以使用AND操作符给WHERE子句附加条件。AND代表“且”的意思,即要满足AND连接的两个表达式才能返回结果。例如:WHERE stu_age >= 17 AND stu_age <= 20需要属性同时满足stu_age >= 17 和stu_age <= 20才能返回结果。
表2-2 WHERE子句操作符
OR操作符代表“或”的意思,即满足OR连接的两个表达式的其中一个就能返回结果。它指示DBMS检索匹配任一条件的行。事实上,许多DBMS在WHERE子句的第一个条件得到满足的情况下,就不再计算第二个条件。查询年龄为17或者20岁的学生信息的WHERE子句为:WHERE stu_age=17 OR stu_age=20。
WHERE子句可以包含任意数目的AND和OR操作符,允许两者结合以进行复杂、高级的过滤。任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认求值顺序,否则会导致错误发生。
要检查某个范围内的值,可以使用BETWEEN操作符。其语法与其他WHERE子句的操作符稍有不同,因为它需要两个值,即范围的开始值和结束值。例如,BETWEEN操作符可用来检索年龄在18~20之间的所有学生。程序清单2.6说明了如何使用BETWEEN操作符。
程序清单2.6 WHERE子句中BETWEEN关键字的使用
IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取一组由逗号分隔、括在圆括号中的合法值。程序清单2.7说明了如何使用这个操作符。
程序清单2.7 WHERE子句中IN关键字的使用
程序清单2.7中的SELECT语句检索籍贯为Jiangxi或者Shanghai的所有学生。IN操作符后跟由逗号分隔的合法值,这些值必须括在圆括号中。
WHERE子句中的NOT操作符有且只有一个功能,即否定其后所跟的任何条件。因为NOT从不单独使用(它总是与其他操作符一起使用),所以它的语法与其他操作符有所不同。NOT关键字可以用在要过滤的列前。程序清单2.8中的SELECT语句检索除一年级以外的所有学生信息。
程序清单2.8 WHERE子句中NOT关键字的使用
前面介绍的所有操作符都是针对已知值进行过滤的。不管是匹配一个值还是多个值,检验大于还是小于已知值,或者检查某个范围的值,其共同点是过滤中使用的值都是已知的。但是,这种过滤方法并不是任何时候都好用。例如,怎样查找姓名中含Li的所有学生信息?用简单的比较操作符肯定不行,必须使用通配符。利用通配符,可以创建比较特定数据的搜索模式。
在字符串上可以使用 LIKE 操作符来实现模式匹配。我们使用以下两个特殊字符来描述模式。
●百分号(%):匹配任意子串。
●下划线(_):匹配任意一个字符。
模式匹配是大小写敏感的,也就是说,大写字符与小写字符不匹配,反之亦然。为了说明模式匹配,考虑以下例子。
●‘SQL%’匹配任何以“SQL”开头的字符串。
●‘%SQL%’匹配任何包含“SQL”子串的字符串,例如‘MYSQL5.0’和‘SQL Server’。
●‘_ _ _’匹配只含三个字符的字符串。
在SQL 中用比较运算符 LIKE 来进行模式匹配。例如,要找出所有姓名以X开头的学生信息,可使用程序清单2.9中的SELECT语句。
程序清单2.9 WHERE子句中LIKE关键字的使用
我们现在已经知道如何用SELECT语句的WHERE子句过滤返回的数据。我们还学习了如何检验相等、不相等、大于、小于、值的范围,如何用AND和OR操作符组合成WHERE子句,还了解了如何明确地管理求值顺序,如何使用IN和NOT操作符以及如何在WHERE子句中使用SQL通配符。
接下来介绍GROUP BY子句。GROUP BY子句的目的是将行收集到组中,并以某种方式汇总组中的行。GROUP BY子句中给出的一个或多个属性是用来构造分组的。在GROUP BY子句中的所有属性上取值相同的元组被分在一个组中,最终每个组只返回一行。
分组是使用SELECT语句的GROUP BY子句建立的,可以从程序清单2.10所示的例子理解分组的含义。
程序清单2.10 GROUP BY子句的使用
程序清单2.10中的SELECT语句指定了两个列:stu_sex包含学生的性别;nums为计算字段(用COUNT(stu_sex)函数建立),表示统计男女学生各有多少人。GROUP BY子句指示DBMS按stu_sex排序并分组数据。因为使用了GROUP BY,就不必指定要计算和估值的每个组了,系统会自动完成这一过程。GROUP BY子句指示DBMS分组数据,然后对每个组而不是整个结果集进行聚集。
除了能用GROUP BY分组数据外,SQL还允许过滤分组,规定包括哪些分组、排除哪些分组。例如,你可能想要列出至少有100个学生的所有课程。为此,必须基于完整的分组而不是个别的行进行过滤。我们已经看到了WHERE子句的作用,但是,在这个例子中WHERE不能完成任务,因为WHERE子句过滤指定的行而不是分组。而事实上WHERE子句没有分组的概念。
不使用WHERE子句使用什么呢?SQL为此提供了另一个子句,即HAVING子句。HAVING子句非常类似于WHERE子句。事实上,目前为止所有类型的WHERE子句都可以用HAVING子句来替代。唯一的差别是,WHERE子句过滤行,而HAVING子句过滤分组。与SELECT子句类似,任何出现在HAVING子句中但没有被聚集的属性必须出现在GROUP BY子句中,否则查询就会被当成是错误的。
那么,怎么过滤分组呢?请看程序清单2.11查询课程学生人数大于等于2的课程id的例子。
程序清单2.11 HAVING过滤分组
我们已经知道如何使用GROUP BY子句对多组数据进行汇总计算,返回每个组的结果,还看到了如何使用HAVING子句过滤特定的组。
在进行数据库查询时,有时只想返回有限数量的行,不需要返回查询的所有结果。那么怎么输出限制数量的行呢?不同的DBMS使用不同的语法来实现这一限制。MySQL、PostgreSQL和SQLite支持LIMIT子句,Oracle和SQL Server使用不同的语法来实现这一功能。程序清单2.12表示在不同的DBMS上返回学生表的前三行。
程序清单2.12 不同数据库返回有限数量的行
下面回顾SELECT语句中子句的顺序。表2-3是在SELECT语句中使用子句时必须遵循的次序。
表2-3 SELECT子句及其顺序
到目前为止,我们所看到的所有SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。为什么要这样做呢?理解这个概念的最好方法是考察以下几个例子。
选修课程表Elecourses包括课程id、学生id,课程的详细信息存储在课程表Courses当中,学生的信息存储在学生表Students中。
查询选修了课程id为2的所有学生的姓名示例如程序清单2.13所示。
程序清单2.13 SQL子查询示例1
在SELECT语句中,子查询总是从内向外处理。在处理上面的SELECT语句时,DBMS实际上执行了两个操作。首先,它执行如程序清单2.14所示的查询。
程序清单2.14 SQL子查询示例2
此查询返回选修了课程id为2的所有学生的id(10000,10005,10006)。然后,这两个值以IN操作符要求的逗号分隔的格式传递给外部查询的WHERE子句。外部查询如程序清单2.15所示。
程序清单2.15 SQL子查询示例3
2.1.3 SQL表的连接
本节继续介绍SQL。现在我们考虑一些更复杂的连接,包括它们的含义和使用方法。现在来看三种连接:自连接(SELF JOIN)、自然连接(NATURAL JOIN)和外连接(OUTER JOIN)。
1.自连接
自连接是指存在两张表结构和数据内容完全一样的表,在做数据处理的时候,我们通常分别对它们进行重命名来加以区分,然后进行关联。
假如要找出与Xiao Chen同龄的所有学生的学号和姓名。要求首先查询Xiao Chen的年龄,然后找出与他同龄的所有学生。程序清单2.16是解决此问题的一种方法。
程序清单2.16 子查询语句
这是第一种解决方案,使用了子查询。现在来看使用自连接的相同查询,如程序清单2.17所示。
程序清单2.17 自连接语句
此查询中需要的两个表实际上是相同的表,可以看到的是Students表在FROM 子句中出现了两次。DBMS不知道你引用的是哪个Students表,要解决此问题,需要使用别名,将这些别名用作表名。例如,SELECT语句使用s1前缀明确给出所需列的全名。否则DBMS将返回错误,因为名为stu_id、stu_name、stu_age的列各有两个,DBMS不知道想要的是哪一列(即使它们其实是同一列)。WHERE首先连接两个表,然后按第二个表中的stu_name过滤数据,返回所需的数据。
使用子查询和自连接虽然最终的结果是相同的,但许多DBMS处理连接远比处理子查询快得多。
2.自然连接
在我们的查询示例中,需要从几张表中组合信息,而在表中至少具有一种相同名称的所有属性。
为了在这种通用情况下简化SQL编程人员的工作,SQL支持一种被称作自然连接的运算,下面我们就来讨论这种运算。事实上,SQL还支持另外几种方式使来自两个或多个关系的信息可以被连接起来。
自然连接运算作用于两个关系,并产生一个关系作为结果。不同于两个关系上的笛卡儿积,它将第一个关系的每个元组与第二个关系的所有元组都进行连接,自然连接只考虑那些在两个关系模式中都出现的属性上取值相同的元组对。因此,回到Students和Elecourses关系的例子上,Students和Elecourses的自然连接计算中只考虑共同属性stu_id上取值相同的元组对。自然连接将表中具有相同名称的列自动进行记录匹配,不必指定任何同等连接条件。
考虑查询“对于所有学生,找出他们的学号、姓名以及所选课程标识”,该查询如程序清单2.18所示。
程序清单2.18 自然连接
在一个SQL查询的FROM子句中,可以用自然连接将多个关系结合在一起,如下所示:
SELECT A1,A2,…,An
FROM r1 NATURAL JOIN r2 NATURAL JOIN … NATURAL JOIN rm
WHERE P;
更为一般地,FROM子句可以为如下形式:
FROM E1,E2,…,En
其中每个Ei都可以表示单个关系和一个包含自然连接的表达式。为了发扬自然连接的优点,同时避免不必要的相等属性带来的危险,SQL提供了一种自然连接的构造形式,允许用户来指定需要哪些列相等。程序清单2.19所示的查询说明了这个特征。
程序清单2.19 JOIN…USING子句
JOIN…USING运算中需要给定一个属性名列表,其两个输入中都必须具有指定名称的属性。
上面介绍了如何表达自然连接,并介绍了JOIN…USING子句,它是一种自然连接的形式,只需要在指定属性上的取值匹配。SQL支持另外一种形式的连接,其中可以指定任意的连接条件。ON条件允许在参与连接的关系上设置通用的谓词。该谓词的写法与WHERE子句谓词类似,只不过使用的是关键词ON而不是WHERE。与USING条件一样,ON条件出现在连接表达式的末尾,如程序清单2.20所示。
程序清单2.20 ON条件关键字
程序清单2.20中的ON条件表明:如果一个来自Students的元组和一个来自Elecourses的元组在stu_id上的取值相同,那么它们是匹配的。上例中的连接表达式与Students NATURE JOIN Elecourses几乎是一样的,因为自然连接运算也需要Students元组和Elecourses元组是匹配的。
实际上,程序清单2.20中的查询与程序清单2.21所示的查询是等价的,它们产生相同的结果。
程序清单2.21 WHERE子句
ON条件可以表示任何SQL谓词,从而使用ON条件的连接表达式就可以表示比自然连接更为丰富的连接条件。然而,正如上例所示,带ON条件的连接表达式的查询可以用不带ON条件的等价表达式来替换,只要把ON子句中的谓词移到WHERE子句中即可。这样看来,ON条件似乎是一个冗余的SQL特征。但是,引入ON条件有两个优点。首先,对于马上要介绍的被称作外连接的这类连接来说,ON条件的表现与WHERE条件是不同的;其次,如果在ON子句中指定连接条件,并在WHERE子句中出现其余的条件,那么这样的SQL查询通常更容易让人读懂。
3.外连接
假设要显示一个所有学生的列表,显示他们的学号、姓名、年级以及他们所选修的课程号。使用查询:SELECT *FROM Students AS s NATURAL JOIN Elecourses AS e好像能检索出所需的信息。但是上述查询与想要的结果是不同的。假设有些学生没有选修任何课程,那么这些学生在Students关系中所对应的元组与Elecourses关系中的任何元组配对都不会满足自然连接的条件,因此这些学生的数据就不会出现在结果中。这样我们就看不到没有选修任何课程的学生的任何信息了。
例如,在Students关系和Elecourses关系中,stu_id为10001的学生Ming Zhi没有选修任何课程。Ming Zhi出现在Students关系中,但是Ming Zhi的stu_id没有出现在 Elecourses的stu_id列中,因此Ming Zhi不会出现在自然连接的结果中。
更为一般地,在参与连接的任何一个或两个关系中的某些元组可能会以这种方式“丢失”。外连接运算与连接运算类似,但通过在结果中创建包含空值元组的方式保留了那些在连接中丢失的元组。
例如,为了保证名为Ming Zhi的学生出现在结果中,可以在连接结果中加入一个元组,它在来自Students关系的所有属性上的值被设置为学生Ming Zhi的相应值,所有剩下的来自Elecourses关系属性上的值被设为NULL,这些属性是cou_id。
实际上有以下三种形式的外连接。
●左外连接(LEFT OUTER JOIN):只保留出现在左外连接运算之前(左边)的关系中的元组。
●右外连接(RIGHT OUTER JOIN):只保留出现在右外连接运算之后(右边)的关系中的元组。
●全外连接(FULL OUTER JOIN):保留出现在两个关系中的元组。
相比而言,为了与外连接运算相区分,我们此前学习的不保留未匹配元组的连接运算被称作内连接(INNER JOIN)运算。
下面详细解释每种形式的外连接是怎样操作的。我们可以按照如下方式进行左外连接运算。首先,像前面那样计算出内连接的结果;然后,对于在内连接的左侧关系中任意一个与右侧关系中任何元组都不匹配的元组t,向连接结果中加入一个元组r,r的构造如下。
●元组r从左侧关系得到的属性被赋为t中的值。
●r的其他属性被赋为空值。
我们可以写出查询“学生的选课信息,包括一门课都没选的学生的选课信息”,如程序清单2.22所示。
程序清单2.22 左外连接示例1
下面是外连接运算的另一个例子,我们可以写出查询“找出所有一门课程也没有选修的学生”,如程序清单2.23所示。
程序清单2.23 左外连接示例2
右外连接和左外连接是对称的。来自右侧关系中的不匹配左侧关系任何元组的元组被补上空值,并被加入右外连接的结果中。我们使用右外连接来重写前面的查询,并交换列出关系的次序,如程序清单2.24所示。
程序清单2.24 右外连接示例
得到的结果是一样的,只不过结果中属性出现的顺序不同。
全外连接是左外连接与右外连接类型的组合。在内连接结果计算出来之后,左侧关系中不匹配右侧关系任何元组的元组被添上空值并加入结果中。类似地,右侧关系中不匹配左侧关系任何元组的元组也被添上空值并加入结果中。
为了把常规连接和外连接区分开来,SQL中把常规连接称作内连接。这样连接子句就可以用INNER JOIN来替换OUTER JOIN,说明使用的是常规连接。然而关键词INNER是可选的,如果JOIN子句中没有使用OUTER前缀,则默认的连接类型是INNER JOIN。因此,SELECT * FROM Students JOIN Elecourses using (cou_id)等价于SELECT * FROM student INNER JOIN takes using (cou_id),类似地,NATURAL JOIN等价于NATURAL INNER JOIN。任意的连接形式(内连接、左外连接、右外连接或全外连接)可以和任意的连接条件(自然连接、USING条件连接或ON 条件连接)进行组合。
关于表的各种连接的对比如表2-4所示。
表2-4 对比表的各种连接
2.1.4 SQL的其他语句
本节介绍如何使用SQL的INSERT语句、UPDATE语句和DELETE语句,以及各种连接的表达、视图、事务、完整性约束、授权等内容。
INSERT用来将行插入(或添加)到数据库表,可以插入完整的行也可以插入行的一部分。
将一个新学生插入到Students表中。存储到表中的每一列的数据在VALUES子句中给出,必须为每一列提供一个值。如果某列没有值,则应该使用NULL值(假定表允许对该列指定空值)。必须以在表定义中出现的次序填充各列,如程序清单2.25所示。
程序清单2.25 INSERT语句插入行
使用INSERT的推荐方法是明确给出表的列名,使用这种方法,还可以省略列,表示可以只给某些列提供值,如程序清单2.26所示。
程序清单2.26 INSERT语句插入行的某些属性
INSERT一般用来向表中插入具有指定列值的行。INSERT还存在另一种形式,可以利用它将SELECT语句的结果插入表中,这就是所谓的INSERT SELECT。顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。假如想把另一个表中的新学生合并到Students表中,不需要每次读取一行再将它用INSERT插入,如程序清单2.27所示。
程序清单2.27 INSERT语句插入SELECT语句的结果
可以使用UPDATE语句更新表中的特定行。举一个简单例子,学号为10006的学生需要更改名字,如程序清单2.28所示。
程序清单2.28 UPDATE语句更新属性
删除请求的表达与查询非常类似。我们只能删除整个元组,不能只删除某些属性上的值。SQL用如下语句表示删除:DELETE FROM r WHERE P。其中P代表一个谓词,r代表一个关系。DELETE 语句首先从r中找出所有使P(t)为真的元组t,然后把它们从r中删除。如果省略WHERE子句,则r中所有元组将被删除。注意,DELETE命令只能作用于一个关系。如果我们想从多个关系中删除元组,必须在每个关系上使用一条DELETE命令。WHERE子句中的谓词可以和SELECT命令的WHERE子句中的谓词一样复杂。在另一种极端的情况下,WHERE子句可以为空,请求DELETE FROM Students;将删除Students关系中的所有元组。Students关系本身仍然存在,但它变成空的了。下面是SQL删除请求的例子。使用DELETE语句从表中删除特定的行,如程序清单2.29所示。
程序清单2.29 DELETE语句删除行
这条语句很容易理解。DELETE FROM指定要删除数据的表名,WHERE子句指定要删除的行。在这个例子中,只删除学号为10006的学生数据。
视图
视图是一个虚拟表,其内容由查询定义。同基本表一样,视图包含一系列带有名称的列和行数据。视图在数据库中并不是以数据值存储集形式存在的。视图中的行和列数据来自定义视图的查询所引用的基本表,并且在引用视图时动态生成。对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其他数据库的一个或多个表,或者其他视图。分布式查询也可用于定义使用多个异类源数据的视图。例如,如果有多台不同的服务器分别存储某单位在不同地区的数据,而需要将这些服务器上结构相似的数据组合起来,这种方式就很有用。视图通常用来集中、简化和自定义每个用户对数据库的不同认识。视图可用作安全机制,方法是允许用户通过视图访问数据,而不授予用户直接访问视图关联的基础表权限。视图还可用于提供向后兼容接口来模拟曾经存在但其架构已更改的基础表。
可以使用SQL语句CREATE VIEW创建视图,其语法格式如程序清单2.30所示。
程序清单2.30 创建视图语法格式
其中<query expression>可以是任何合法的查询语句,v表示视图名。
创建视图后,只在数据字典中存放视图的定义,而其中的子查询SELECT语句并不执行。只有当用户对视图进行操作时,才按照视图的定义将数据从基本表中取出。
事务
事务(transaction)由查询和(或)更新语句的序列组成。SQL标准规定当一条SQL语句被执行时,就隐式地开始了一个事务。
●Commit work:提交当前事务,也就是将该事务所做的更新在数据库中持久保存。在事务被提交后,一个新的事务自动开始。
●Rollback work:回滚当前事务,即撤销该事务中所有SQL语句对数据库的更新。这样,数据库就恢复到执行该事务第一条语句之前的状态。
当在事务执行过程中检测到错误时,事务回滚是有用的。一旦某个事务执行了Commit work,它的影响就不能用Rollback work来撤销了。数据库系统保证在发生诸如某条SQL语句错误、断电、系统崩溃等故障的情况下,如果一个事务还没有完成Commit work,其影响将被回滚。在断电和系统崩溃的情况下,回滚会在系统重启后执行。
完整性约束
CREATE table命令用于定义关系表,CREATE table命令还可以包括完整性约束语句。除了主码约束之外,还有许多其他可以包括在CREATE table命令中的约束。允许的完整性约束包括:
●NOT NULL约束。对于一些属性来说,空值可能是不合适的。考虑Students 关系中的一个元组,其中stu_name是NULL。这样的元组给出了一个未知学生的信息,它不含有有用的信息。在这种情况下,我们希望禁止出现空值,可以用如下声明通过限定属性stu_name的域来排除空值:name varchar(20) NOT NULL。NOT NULL声明禁止在该属性上插入空值。任何可能导致向一个被声明为NOT NULL的属性插入空值的数据库修改都会产生错误信息。
●UNIQUE约束。UNIQUE(Am1,Am2, …, Amn):UNIQUE声明指出属性Am1,Am2, …, Amn形成了一个候选码,即在关系中没有两个元组能在所有列出的属性上取值相同。
●CHECK子句。通常用CHECK子句来保证属性值满足指定的条件,CHECK(P)子句指定一个谓词P,关系中的每个元组都必须满足谓词P。实际上创建了一个强大的类型系统。例如,在创建关系Students的CREATE table命令中的CHECK(stu_age >0)子句将保证stu_age上的取值是正数。
授权
SQL标准包括SELECT、INSERT、UPDATE和DELETE权限。一个创建了新关系的用户将自动被授予该关系上的所有权限。SQL数据定义语言包括授予和收回权限的命令。GRANT语句用来授予权限,语句的基本形式如程序清单2.31所示。
程序清单2.31 GRANT授予权限语法格式
关系上的SELECT权限用于读取关系中的元组。程序清单2.32中的GRANT语句授予数据库用户A和B在Students关系上的SELECT权限。
程序清单2.32 GRANT授予权限示例
关系上的UPDATE权限允许用户修改关系中的任意元组。既可以在关系的所有属性上授予UPDATE权限,又可以只在某些属性上授予UPDATE权限。如果GRANT语句中包括UPDATE权限,将被授予UPDATE权限的属性列表可以出现在紧跟关键字UPDATE的括号中。属性列表是可选项,如果省略属性列表,则授予的是关系中所有属性上的UPDATE权限。
程序清单2.33中的GRANT语句授予用户A和B在Students关系的stu_name属性上的更新权限。
程序清单2.33 GRANT修改权限示例
关系上的INSERT权限允许用户向关系中插入元组。INSERT权限也可以指定属性列表。关系上的DELETE权限允许用户从关系中删除元组。我们使用REVOKE语句来收回权限。如程序清单2.34所示,此语句的形式与GRANT语句几乎相同。
程序清单2.34 REVOKE收回权限语法格式
要收回前面授予的那些权限,REVOKE语句如程序清单2.35所示。
程序清单2.35 REVOKE收回权限示例
视图关系可以定义为包含查询结果的关系。视图是有用的,它可以隐藏不需要的信息,也可以把信息从多个关系收集到一个单一的视图中。
事务是一个查询和更新的序列,它们共同执行某项任务。事务可以被提交或回滚。当一个事务被回滚,该事务执行的所有更新所带来的影响将被撤销。
完整性约束保证授权用户对数据库所做的改变不会破坏数据一致性。参照完整性约束保证出现在一个关系的给定属性集上的值同样出现在另一个关系的特定属性集上。域约束指定了在一个属性上可能取值的集合。这种约束也可以禁止在特定属性上使用空值。
通过SQL授权机制,可以按照在数据库中不同数据值上数据库用户所允许的访问类型对他们进行区分。获得某种形式授权的用户可能允许将此授权传递给其他用户。但是,我们必须注意权限怎样在用户间传递,以保证这样的权限在将来可以被收回。