👋欢迎来到黄铜扳手图书馆

数据库系统(三):关系数据库标准语言SQL

为关系数据库设计的结构化查询语言SQL

关系数据库标准语言SQL

  结构化查询语言(Structured Query Language,SQL)‌是关系数据库的标准语言,也是一个通用的、功能极强的关系数据库语言。其功能不仅仅是查询,而是包括数据库模式创建、数据库数据的插入与修改、数据库安全性完整性定义与控制等一系列功能。本章详细介绍SQL的基本功能,并进一步讲述关系数据库的基本概念。

SQL概述

  自SQL成为国际标准语言以后,各个数据库厂家纷纷推出各自的SQL软件或与SQL的接口软件。这就使大多数数据库均用SQL作为共同的数据存取语言和标准接口,使不同数据库系统之间的互操作有了共同的基础。SQL已成为数据库领域中的主流语言,其意义十分重大。有人把确立SQL为关系数据库语言标准及其后的发展称为是一场革命。

SQL的产生与发展

  SQL是在1974年由Boyce和Chamberlin提出的,最初叫Sequel,并在IBM公司研制的关系数据库管理系统原型System R上实现。由于SQL简单易学,功能丰富,深受用户及计算机工业界欢迎,因此被数据库厂商所采用。经各公司的不断修改、扩充和完善,SQL得到业界的认可。1986年10月,美国国家标准(American National Standard Institute,ANSI)的数据库委员会X3H2批准了SQL作为关系数据库语言的美国标准,同年公布了SQL标准文本(简称SQL-86)。1987年,国际标准化组织(International Organizationfor Standardization,ISO)也通过了这一标准。
  SQL标准从公布以来随数据库技术的发展而不断发展、不断丰富。.1是SQL标准的进展过程。

标准 大致页数 发布日期
SQL/86 1086年10月
SQL/89(FIPS 127-1) 120页 1989年
SQL/92 622页 1992年
SQL 99 (SQL 3) 1700页 1999年
SQL 2003 3600页 2003年
SQL 2008 3777页 2008年
SQL 2011 2010年

表1 SQL标准的进展过程

  2008年、2011年又对SQL2003做了一些修改和补充。可以发现,SQL标准的内容越来越丰富,也越来越复杂。SQL99合计超过1700页。SQL/86和SQL/89都是单个文档。SQL/92和SQL99已经扩展为一系列开放的部分。例如,SQL/92除了SQL基本部分外还增加了SQL调用接口、SQL永久存储模块;而SQL99则进一步扩展为框架、SQL基础部分、SQL调用接口、SQL永久存储模块、SQL宿主语言绑定、SQL外部数据的管理和SQL 对象语言绑定等多个部分。
  目前,没有一个数据库系统能够支持SQL标准的所有概念和特性。大部分数据库系统能支持SQL/92标准中的大部分功能以及SQL99、SQL2003中的部分新概念。同时,许多软件厂商对SQL基本命令集还进行了不同程度的扩充和修改,又可以支持标准以外的一些功能特性。本文不是介绍完整的SQL,而是介绍SQL的基本概念和基本功能。因此,在使用具体系统时要查阅各产品的用户手册。

SQL的特点

  SQL之所以能够为用户和业界所接受并成为国际标准,是因为它是一个综合的、功能极强同时又简洁易学的语言。SQL集数据查询(data query)、数据操纵(data manipulation)、数据定义(data definition)和数据控制(data control)功能于一体,其主要特点包括以下几部分。

综合统一

  数据库系统的主要功能是通过数据库支持的数据语言来实现的。
  非关系模型(层次模型、网状模型)的数据语言一般都分为:
  (1)模式数据定义语言(Schema Data Definition Language,模式 DDL)。
  (2)外模式数据定义语言(Subschema Data Definition Language,外模式DDL或子模式DDL)。
  (3)数据存储有关的描述语言(Data Storage Description Language, DSDL)。
  (4)数据操纵语言(Data Manipulation Language, DML)。
  它们分别用于定义模式、外模式、内模式和进行数据的存取与处置。当用户数据库投入运行后,如果需要修改模式,必须停止现有数据库的运行,转储数据,修改模式并编译后再重装数据库,十分麻烦。

  SQL集数据定义语言、数据操纵语言、数据控制语言的功能于一体,语言风格统一,可以独立完成数据库生命周期中的全部活动,包括以下一系列操作要求:
  ◦定义和修改、删除关系模式,定义和删除视图,插入数据,建立数据库。
  ◦对数据库中的数据进行查询和更新。
  ◦数据库重构和维护。
  ◦数据库安全性、完整性控制,以及事务控制。
  ◦嵌入式SQL和动态SQL定义。
  这就为数据库应用系统的开发提供了良好的环境。特别是用户在数据库系统投入运行后还可根据需要随时地、逐步地修改模式,并不影响数据库的运行,从而使系统具有良好的可扩展性。
  另外,在关系模型中实体和实体间的联系均用关系表示,这种数据结构的单一性带来了数据操作符的统一性,查找、插入、删除、更新等每一种操作都只需一种操作符, 从而克服了非关系系统由于信息表示方式的多样性带来的操作复杂性。例如,在DBTG网状数据库系统中,需要两种插入操作符:STORE用来把记录存入数据库,CONNECT用来把记录插入系值(系值是网状数据库中记录之间的一种联系方式)以建立数据之间的联系。

高度非过程化

  非关系数据模型的数据操纵语言是“面向过程”的语言,用“过程化”语言完成某项请求必须指定存取路径。而用SQL进行数据操作时,只要提出“做什么”,而无须指明“怎么做”,因此无须了解存取路径。存取路径的选择以及SQL的操作过程由系统自动完成。 这不但大大减轻了用户负担,而且有利于提高数据独立性。

面向集合的操作方式

  非关系数据模型采用的是面向记录的操作方式,操作对象是一条记录。例如查询所有平均成绩在80分以上的学生姓名,用户必须一条一条地把满足条件的学生记录找出来(通常要说明具体处理过程,即按照哪条路径,如何循环等)。而SQL采用集合操作方式,不仅操作对象、查找结果可以是元组的集合,而且一次插入、删除、更新操作的对象也可以是元组的集合。

以同一种语法结构提供多种使用方式

  SQL既是独立的语言,又是嵌入式语言。作为独立的语言,它能够独立地用于联机交互的使用方式,用户可以在终端键盘上直接键入SQL命令对数据库进行操作;作为嵌入式语言,SQL语句能够嵌入到高级语言(例如C、C++、Java)程序中,供程序员设计程序时使用。而在两种不同的使用方式下,SQL的语法结构基本上是一致的。这种以统一的语法结构提供多种不同使用方式的做法,提供了极大的灵活性与方便性。

语言简洁、易学易用

  SQL功能极强,但由于设计巧妙,语言十分简洁,完成核心功能只用了9个动词,如表2所示。SQL接近英语口语,因此易于学习和使用。

SQL功能 动词
数据查询 SELECT
数据定义 CREATEDROPALTER
数据操纵 INSERTUPDATEDELETE
数据控制 GRANTREVOKE

表2 SQL的动词

SQL的基本概念

  支持SQL的关系数据库管理系统同样支持关系数据库三级模式结构。如图1所示,其中外模式包括若干视图(view)‌和部分基本表(base table),数据库模式包括若干基本表,内模式包括若干存储文件(stored file)

S Q L 视图1 基本表2 存储文件1 视图2 基本表1 基本表3 基本表4 存储文件2

图1 SQL对关系数据库模式的支持

  用户可以用SQL对基本表和视图进行查询或其他操作,基本表和视图一样,都是关系。
  基本表是本身独立存在的表,在关系数据库管理系统中一个关系就对应一个基本表。一个或多个基本表对应一个存储文件,一个表可以带若干索引,索引也存放在存储文件中。存储文件的逻辑结构组成了关系数据库的内模式。存储文件的物理结构对最终用户是隐蔽的。
  视图是从一个或几个基本表导出的表。它本身不独立存储在数据库中,即数据库中只存放视图的定义而不存放视图对应的数据。这些数据仍存放在导出视图的基本表中,因此视图是一个虚表。视图在概念上与基本表等同,用户可以在视图上再定义视图。
  下面将逐一介绍各SQL语句的功能和格式。为了突出基本概念和基本功能,略去了许多语法细节。各个关系数据库管理系统产品在实现标准SQL时各有差别,与SQL标准的符合程度也不相同,一般在85%以上。因此,具体使用某个关系数据库管理系统产品时,还应参阅系统提供的有关手册。

学生—课程数据库

  本章以学生—课程数据库为例来讲解SQL的数据定义、数据操纵、数据查询和数据控制语句。
  为此,首先要定义一个学生—课程模式S-T(见例1)。学生-课程数据库中包括以下三个表,它们的定义见例5例6例7
  ◦学生表:Student(Sno,Sname,Ssex,Sage,Sdept)
  ◦课程表:Course(Cno,Cnme,Cpno,Ccredit)
  ◦学生选课表:SC(Sno,Cno,Grade)
  关系的主码加下划线表示。各个表中的数据示例如表3表4表5所示。

  Student

学号
Sno
姓名
Sname
性别
Ssex
年龄
Sage
所在系
Sdept
201215121 李勇 20 CS
201215122 刘晨 19 CS
201215123 王敏 18 MA
201215125 张立 19 IS

表3 Student表格中的数据示例

  Course

课程号
Cno
课程名
Cname
先行课
Cpno
学分
Ccredit
1 数据库 5 4
2 数学 2
3 信息系统 1 4
4 操作系统 6 3
5 数据结构 7 4
6 数据处理 2
7 PASCAL语言 6 4

表4 Course表格中的数据示例

  SC

学号
Sno
课程号
Cno
成绩
Grade
201215121 1 92
201215121 2 85
201215121 3 88
201215122 2 90
201215122 3 80

表5 SC表格中的数据示例

数据定义

  关系数据库系统支持三级模式结构,其模式、外模式和内模式中的基本对象有模式、表、视图和索引等。因此SQL的数据定义功能包括模式定义、表定义、视图和索引的定义, 如表6所示。

操作对象 操作方式
创建 删除 修改
模式 CREATE SCHEMA DROP SCHEMA
CREATE TABLE DROP TABLE ALTER TABLE
视图 CREATE VIEW DROP VIEW
索引 CREATE INDEX DROP INDEX ALTER INDEX

表6 SQL的数据定义语句

  SQL标准不提供修改模式定义和修改视图定义的操作。用户如果想修改这些对象,只能先将它们删除然后再重建。SQL标准也没有提供索引相关的语句,但为了提高查询效率,商用关系数据库管理系统通常都提供了索引机制和相关的语句,如表6中创建、删除和修改索引等。
  在早期的数据库系统中,所有数据库对象都属于一个数据库,也就是说只有一个命名空间。现代的关系数据库管理系统提供了一个层次化的数据库对象命名机制,如图2所示。一个关系数据库管理系统的实例(instance)‌中可以建立多个数据库,一个数据库中可以建立多个模式,一个模式下通常包括多个表、视图和索引等数据库对象。

数据 有的系统称为目 模式 表以及视 索引等

图2 数据库对象命名机制的层次结构

模式的定义与删除

定义模式

  在SQL中,模式定义语句如下:

1
CREATE SCHEMA <模式名> AUTHORIZATION <用户名>;

  如果没有指定<模式名>,那么<模式名>隐含为<用户名>
  要创建模式,调用该命令的用户必须拥有数据库管理员权限,或者获得了数据库管理员授予的CREATE SCHEMA的权限。

  例1:为用户WANG定义一个学生—课程模式S-T

1
CREATE SCHEMA "S-T" AUTHORIZATION WANG;

  例2

1
CREATE SCHEMA AUTHORIZATION WANG;

  该语句没有指定<模式名>,所以<模式名>隐含为用户名WANG

  定义模式实际上定义了一个命名空间,在这个空间中可以进一步定义该模式包含的数据库对象,例如基本表、视图、索引等。
  这些数据库对象可以用表6中相应的CREATE语句来定义。
  目前,在CREATE SCHEMA中可以接受CREATE TABLECREATE VIEWGRANT子句。也就是说用户可以在创建模式的同时在这个模式定义中进一步创建基本表、视图,定义授权。即

1
CREATE SCHEMA <模式名> AUTHORIZATION <用户名> [<表定义子句>|<视图定义子句>|<授权定义子句>];

  例3:为用户ZHANG创建一个模式TEST,并且在其中定义一个表TAB1

1
2
3
4
5
6
7
CREATE SCHEMA TEST AUTHORIZATION ZHANG
CREATE TABLE TAB 1 (COL 1 SMALLINT,
                    COL2 INT,
                    COL3 CHAR(20),
                    COL4 NUMERIC(10,3),
                    COL5 DECIMAL(5,2) 
                    );

删除模式

  在SQL中,删除模式语句如下:

1
DROP SCHEMA <模式名><CASCADE|RESTRICT>;

  其中CASCADERESTRICT两者必选其一。选择了CASCADE(级联),表示在删除模式的同时把该模式中所有的数据库对象全部删除;选择了RESTRICT(限制),表示如果表示如果该模式中已经定义了下属的数据库对象(如表、视图等),则拒绝该删除语句的执行。只有当该模式中没有任何下属的对象时才能执行DROP SCHEMA语句。

  例4

1
DROP SCHEMA ZHANG CASCADE;

  该语句删除了模式ZHANG,同时,该模式中已经定义的表TAB1也被删除了。

基本表的定义、删除与修改

定义基本表

  创建了一个模式就建立了一个数据库的命名空间,一个框架。在这个空间中首先要定义的是该模式包含的数据库基本表。
  SQL语言使用CREATE TABLE语句定义基本表,其基本格式如下:

1
2
3
4
CREATE TABLE <表名>(<列名><数据类型> [列级完整性约束条件]
                   [<列名><数据类型> [列级完整性约束条件]]
                   ...
                   [,<表级完整性约束条件>]);

  建表的同时通常还可以定义与该表有关的完整性约束条件,这些完整性约束条件被存入系统的数据字典中,当用户操作表中数据时由关系数据库管理系统自动检查该操作是否违背这些完整性约束条件。如果完整性约束条件涉及该表的多个属性列,则必须定义在表级上,否则既可以定义在列级也可以定义在表级。

  例5:建立一个“学生”表Student

1
2
3
4
5
6
7
CREATE TABLE Student
    (Sno CHAR(9) PRIMARY KEY, /*列级完整性约束条件,Sno是主码*/
    Sname CHAR(20) UNIQUE,    /* Sname 取唯一值*/
    Ssex CHAR(2),
    Sage SMALLINT,
    Sdept CHAR(20)
    );

  系统执行该CREATE TABLE语句后,就在数据库中建立一个新的空“学生”表Student,并将有关“学生”表的定义及有关约束条件存放在数据字典中。

  例6:建立一个课程表Course。

1
2
3
4
5
6
7
8
CREATE TABLE Course
    (Cno CHAR(4) PRIMARY KEY, /*列级完整性约束条件,Cno是主码*/
     Cname CHAR(40) NOT NULL, /*列级完整性约束条件,Cname不能取空值*/
     Cpno CHAR(4),            /* Cpno的含义是先修课*/
     Ccredit SMALLINT,
     FOREIGN KEY (Cpno) REFERENCES Course(Cno)
        /*表级完整性约束条件,Cpno是外码,被参照表是Course,被参照列是Cno。*/
    );

  本例说明参照表和被参照表可以是同一个表。

  例7:建立学生选课表SC。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE TABLE SC 
    (Sno CHAR(9),
    Cno CHAR(4),
    Grade SMALLINT,
    PRIMARY KEY (Sno,Cno), /*主码由两个属性构成,必须作为表级完整性进行定义*/
    FOREIGN KEY (Sno) REFERENCES Student(Sno), 
        /*表级完整性约束条件,Sno是外码,被参照表是Student */
    FOREIGN KEY (Cno) REFERENCES Course(Cno)
        /*表级完整性约束条件,Cno是外码,被参照表是Course*/
    );

数据类型

  关系模型中一个很重要的概念是域。每一个属性来自一个域,它的取值必须是域中的值。在SQL中域的概念用数据类型来实现。定义表的各个属性时需要指明其数据类型及长度。SQL标准支持多种数据类型,表7列出了几种常用数据类型。要注意,不同的关系数据库管理系统中支持的数据类型不完全相同。

数据类型 含义
CHAR(n),CHARACTER(n) 长度为n的定长字符串
VARCHAR(n),CHARACTERVARYING(n) 最大长度为n的变长字符串
CLOB 字符串大对象
BLOB 二进制大对象
INT,INTEGER 长整数(4字节)
SMALLINT 短整数(2字节)
BIGINT 大整数(8字节)
NUMBERIC(p,d) 定点数,由p位数字(不包括符号、小数点)组成,小数点后面有d位数字
DECIMAL(p,d),DEC(p,d) NUMERIC
REAL 取决于机器精度的单精度浮点数
DOUBLE PRECISION 取决于机器精度的双精度浮点数
FLOAT(n) 可续西安精度的浮点数,精度至少为n位数字
BOOLEAN 逻辑布尔量
DATE 日期,包含年、月、日,格式为YYYY-MM-DD
TIME 时间,包含一日的时、分、秒,格式为HH:MM:SS
TIMESTAMP 时间戳类型
INTERVAL 时间间隔类型

表7 数据类型

  一个属性选用哪种数据类型要根据实际情况来决定,一般要从两个方面来考虑,一是取值范围,二是要做哪些运算。例如,对于年龄(Sage)属性,可以采用CHAR(3)作为数据类型,但考虑到要在年龄上做算术运算(如求平均年龄),所以要采用整数作为数据类型,因为在CHAR(n)数据类型上不能进行算术运算。整数又有长整数和短整数两种,因为一个人的年龄在百岁左右,所以选用短整数作为年龄的数据类型。

模式与表

  每一个基本表都属于某一个模式,一个模式包含多个基本表。当定义基本表时一般可以有三种方法定义它所属的模式。例如在例3.1中定义了一个学生—课程模式S-T。现在要在S-T中定义Student、CourceSC等基本表。

  方法一,在表名中明显地给出模式名。

1
2
3
CREATE TABLE "S-T".Student(···); /* Student所属的模式是S-T */
CREATE TABLE "S-T".Course(···);  /* Course所属的模式是S-T */
CREATE TABLE "S-T".SC(···);      /* SC所属的模式是S-T */

  方法二,在创建模式语句中同时创建表,如例3所示。
  方法三,设置所属的模式,设置所属的模式,这样在创建表时表名中不必给出模式名。

  当用户创建基本表(其他数据库对象也一样)时若没有指定模式,系统根据搜索路径(search path)来确定该对象所属的模式。
  搜索路径包含一组模式列表,关系数据库管理系统会使用模式列表中第一个存在的模式作为数据库对象的模式名。若搜索路径中的模式名都不存在,系统将给出错误。使用下面的语句可以显示当前的搜索路径:

1
SHOW search_path;

  搜索路径的当前默认值是$user,PUBLIC。其含义是首先搜索与用户名相同的模式名,如果该模式名不存在,则使用PUBLIC模式。
  数据库管理员也可以设置搜索路径,例如:

1
SET search_path TO "S-T",PUBLIC;

  然后,定义基本表:

1
CREATE TABLE Student (···);

  实际结果是建立了S-T.Student基本表。因为关系数据库管理系统发现搜索路径中第一 个模式名S-T存在,就把该模式作为基本表Student所属的模式。

修改基本表

  随着应用环境和应用需求的变化,有时需要修改已建立好的基本表。SQL语言用ALTER TABLE语句修改基本表,其一般格式为

1
2
3
4
5
6
ALTER TABLE <表名>
    [ADD [COLUMN] <新列名> <数据类型> [完整性约束]]
    [ADD <表级完整性约束>]
    [DROP [COLUMN] <列名> [CASCADE|RESTRICT]]
    [DROP CONSTRAINT <完整性约束名> [CASCADE|RESTRICT]]
    [ALTER COLUMN <列名> <数据类型>];

  其中<表名>是要修改的基本表,ADD子句用于增加新列、新的列级完整性约束条件和新的表级完整性约束条件。DROP COLUMN子句用于删除表中的列,如果指定了CASCADE短语,则自动删除引用了该列的其他对象,比如视图;如果指定了RESTRICT短语,则如果该列被其他对象引用,RDBMS将拒绝删除该列。DROP CONSTRAINT子句用于删除指定的完整性约束条件。ALTER COLUMN子句用于修改原有的列定义,包括修改列名和数据类型。

  例8:向Student表增加“入学时间”列,其数据类型为日期型。

1
ALTER TABLE Student ADD Sentrance DATE;

  不论基本表中原来是否已有数据,新增加的列一律为空值。

  例9:]将年龄的数据类型由字符型(假设原来的数据类型是字符型)改为整数。

1
ALTER TABLE Student ALTER COLUMN Sage INT;

  例10:增加课程名称必须取唯一值的约束条件。

1
ALTER TABLE Course ADD UNIQUE(Cname);

删除基本表

  当某个基本表不再需要时,可以使用DROP TABLE语句删除它。其一般格式为:

1
DROP TABLE <表名> [RESTRICT|CASCADE];

  若选择RESTRICT,则该表的删除是有限制条件的。欲删除的基本表不能被其他表的约束所引用(如CHECKFOREIGN KEY等约束),不能有视图,不能有触发器(trigger),不能有存储过程或函数等。如果存在这些依赖该表的对象,则此表不能被删除。
  若选择CASCADE,则该表的删除没有限制条件。在删除基本表的同时,相关的依赖对象,例如视图,都将被一起删除。
  默认情况是RESTRICT

  例11:删除Student表。

1
DROP TABLE Student CASCADE;

  基本表定义一旦被删除,不仅表中的数据和此表的定义将被删除,而且此表上建立的索引、触发器等对象一般也都将被删除。有的关系数据库管理系统还会同时删除在此表上建立的视图。如果欲删除的基本表被其他基本表所引用,则这些表也可能被删除。例如SC表通过外码Sno引用Student,则执行例例11Student表被删除,SC也被级联删除。因此执行删除基本表的操作一定要格外小心。

  例12:若表上建有视图,选择RESTRICT时表不能删除;选择CASCADE时可以删除表,视图也自动被删除。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
CREATE VIEW IS_Student /* Student表上建立视图 */
AS
SELECT Sno,Sname,Sage
FROM Student
WHERE Sdept='IS';

DROP TABLE Student RESTRICT; /* 删除Student表 */
--ERROR: cannot drop table Student because other objects depend on it 
/* 系统返回错误信息,存在依赖该表的对象,此表不能被删除 */
                             
DROP TABLE Student CASCADE; /* 删除 Student 表 */
--NOTICE: drop cascades to view IS Student
/* 系统返回提示,此表上的视图也被删除 */

SELECT * FROM IS_Student;
--ERROR: relation "IS_Student" does not exist

  注意:不同的数据库产品在遵循SQL标准的基础上具体实现细节和处理策略会与标准有差别。

  下面就SQL 2011标准对DROP TABLE的规定,对比分析Kingbase ES、Oracle 12c Release 1(12.1)、MS SQL Server 2012这三种数据库产品对DROP TABLE的不同处理策略。
  表8中的R表示RESTRICT,即DROP TABLE <基本表名> RESTRICT;C表示CASCADE,即DROP TABLE <基本表名> CASCADE;其中Oracle 12c没有RESTRICT选项;SQL Server没有RESTRICTCASCADE选项。

序号 标准及主流数据库
的处理方式
依赖基本表的对象
SQL2011 Kingbase ES Oracle 12c MS SQL Server 2012
RCRCC
1索引无规定
2视图××
保留

保留

保留
3DEFAULT,PRIMARY KEY,CHECK(只含该表的列),NOT NULL等约束
4外码FOREIGN KEY××××
5触发器TRIGGER××
6函数或存储过程×
保留

保留

保留

保留

保留

表8 DROP TABLE时,SQL2011与3个关系数据库管理系统的处理策略比较

  “×”表示不能删除基本表,“√”表示能删除基本表,“保留”表示删除基本表后,还保留依赖对象。从比较表中可以知道:
  (1)对于索引,删除基本表后,这三个关系数据库管理系统都自动删除该基本表上己经建立的所有索引。
  (2)对于视图,Oracle 12c与SQL Server 2012是删除基本表后,还保留此基本表上的视图定义,但是己经失效。Kingbase ES分两种情况,若删除基本表时带RESTRICT选项, 则不可以删除基本表;若删除基本表时带CASCADE选项,则可以删除基本表,同时也删 除视图。Kingbase ES的这种策略符合SQL 2011标准。
  (3)对于存储过程和函数,删除基本表后,这三个数据库产品都不自动删除建立在此基本表上的存储过程和函数,但是已经失效。
  (4)如果欲删除的基本表上有触发器,或者被其他基本表的约束所引用(CHECKFOREIGN KEY等),读者可以从比较表中得到这三个系统的处理策略,这里就不一一说明了。同样,对于其他的SQL语句,不同的数据库产品在处理策略上会与标准有所差别。因此,如果发现本文中个别例子在某个数据库产品上不能通过时,请读者参见有关产品的用户手册,适当修改即可。

索引的建立与删除

  当表的数据量比较大时,查询操作会比较耗时。建立索引是加快查询速度的有效手段。数据库索引类似于图书后面的索引,能快速定位到需要查询的内容。用户可以根据应用环境的需要在基本表上建立一个或多个索引,以提供多种存取路径,加快查找速度。
  数据库索引有多种类型,常见索引包括顺序文件上的索引、B+树索引、散列(hash)索引、位图索引等。顺序文件上的索引是针对按指定属性值升序或降序存储的关系,在该属性上建立一个顺序索引文件,索引文件由属性值和相应的元组指针组成。B+树索引是将索引属性组织成B+树形式,B+树的叶结点为属性值和相应的元组指针。B+树索引具有动态平衡的优点。散列索引是建立若干个桶,将索引属性按照其散列函数值映射到相应桶中,桶中存放索引属性值和相应的元组指针。散列索引具有查找速度快的特点。位图索引是用位向量记录索引属性中可能出现的值,每个位向量对应一个可能值。
  索引虽然能够加速数据库查询,但需要占用一定的存储空间,当基本表更新时,索引要进行相应的维护,这些都会增加数据库的负担,因此要根据实际应用的需要有选择地创建索引。目前SQL标准中没有涉及索引,但商用关系数据库管理系统一般都支持索引机制,只是不同的关系数据库管理系统支持的索引类型不尽相同。
  一般说来,建立与删除索引由数据库管理员或表的属主(owner),即建立表的人负责完成。关系数据库管理系统在执行查询时会自动选择合适的索引作为存取路径,用户不必也不能显式地选择索引。索引是关系数据库管理系统的内部实现技术,属于内模式的范畴。

建立索引

  在SQL语言中,建立索引使用CREATE INDEX语句,其一般格式为

1
2
CREATE [UNIQUE] [CLUSTER] INDEX <索引名>
ON <表名> (<列名>[<次序>][,<列名>[<次序>]]···);

  其中,<表名>是要建索引的基本表的名字。索引可以建立在该表的一列或多列上,各列名之间用逗号分隔。每个<列名>后面还可以用<次序>指定索引值的排列次序,可选ASC(升序)或DESC(降序),默认值为ASC
  UNIQUE表明此索引的每一个索引值只对应唯一的数据记录。
  CLUSTER表示要建立的索引是聚簇索引。有关聚簇索引的概念在数据库系统(五):数据库设计中介绍。

  例13:为学生—课程数据库中的StudentCourseSC三个表建立索引。其中Student表按学号升序唯一索引,Course表按课程号号升序建唯-索引,SC表按学号升序和课程号降序建唯一索引。

1
2
3
CREATE UNIQUE INDEX Stusno ON Student(Sno);
CREATE UNIQUE INDEX Coucno ON Course(Cno);
CREATE UNIQUE INDEX SCno ON SC(Sno ASC,Cno DESC);

修改索引

  对于已经建立的索引,如果需要对其重新命名,可以使用ALTER INDEX语句。其一般格式为

1
ALTER INDEX <旧索引名> RENAME TO <新索引名>;

  例14:将SC表的SCno索引名改为SCSno

1
ALTER INDEX SCno RENAME TO SCSno;

删除索引

  索引一经建立就由系统使用和维护,不需用户干预。建立索引是为了减少查询操作的时间,但如果数据增、删、改频繁,系统会花费许多时间来维护索引,从而降低了查询效率。这时可以删除一些不必要的索引。

  在SQL中,删除索引使用DROP INDEX语句,其一般格式为

1
DROP INDEX <索引名>;

  例15:删除Student表的Stusname索引。

1
DROP INDEX Stusname;

  删除索引时,系统会同时从数据字典中删去有关该索引的描述。

数据字典

  数据字典是关系数据库管理系统内部的一组系统表,它记录了数据库中所有的定义信息,包括关系模式定义、视图定义、索引定义、完整性约束定义、各类用户对数据库的操作权限、统计信息等。关系数据库管理系统在执行SQL的数据定义语句时,实际上就是在更新数据字典表中的相应信息。在进行查询优化和查询处理时,数据字典中的信息是其重要依据。

数据查询

  数据查询是数据库的核心操作。SQL提供了SELECT语句进行数据查询,该语句具有灵活的使用方式和丰富的功能。其一般格式为

1
2
3
4
5
SELECT [ALL|DISTINCT] <目标列表达式> [,<目标列表达式>] ···
FROM <表名或视图名>[,<表名或视图名>]|(<SELECT语句>) [AS] <别名>
[WHERE <条件表达式>]
[GROUP BY <列名1> [HAVING <条件表达式>]]
[ORDER BY <列名2>  [ASC|DESC]];

  整个SELECT语句的含义是,根据WHERE子句的条件表达式从FROM子句指定的基本表、视图或派生表中找出满足条件的元组,再按SELECT子句中的目标列表达式选出元组中的属性值形成结果表。
  如果有GROUP BY子句,则将结果按<列名1>的值进行分组,该属性列值相等的元组为一个组。通常会在每组中作用聚集函数。如果GROUP BY子句带HAVING短语,则只 有满足指定条件的组才予以输出。
  如果有ORDER BY子句,则结果表还要按<列名2>的值的升序或降序排序。SELECT语句既可以完成简单的单表查询,也可以完成复杂的连接查询和嵌套查询。 下面以学生—课程数据库为例说明SELECT语句的各种用法。

单表查询

  单表查询是指仅涉及一个表的查询。

选择表中的若干列

  选择表中的全部或部分列即关系代数的投影运算。

1. 查询指定列

  在很多情况下,用户只对表中的一部分属性列感兴趣,这时可以通过在SELECT子句的<目标列表达式>中指定要查询的属性列。

  例16:查询全体学生的学号与姓名。

1
2
SELECT Sno,Sname
FROM Student;

  该语句的执行过程可以是这样的:从Student表中取出一个元组,取出该元组在属性SnoSname上的值,形成一个新的元组作为输出。对Student表中的所有元组做相同的处理,最后形成一个结果关系作为输出。

  例17:查询全体学生的姓名、学号、所在系。

1
2
SELECT Sname,Sno,Sdept
FROM Student;

  <目标列表达式>中各个列的先后顺序可以与表中的顺序不一致。用户可以根据应用的需要改变列的显示顺序。本例中先列出姓名,再列出学号和所在系。

2. 查询全部列

  将表中的所有属性列都选出来有两种方法,一种方法就是在SELECT关键字后列出所有列名;如果列的显示顺序与其在基表中的顺序相同,也可以简单地将<目标列表达式>指定为*

  例18:查询全体学生的详细记录。

1
2
SELECT *
FROM Student;

  等价于

1
2
SELECT Sno,Sname,Ssex,Sage,Sdept
FROM Student;

3. 查询经过计算的值

  SELECT子句的<目标列表达式>不仅可以是表中的属性列,也可以是表达式。

  例19:查询全体学生的姓名及其出生年份。

1
2
SELECT Sname,2014-Sage /*查询结果的第2列是一个算术表达式*/
FROM Student;

  查询结果中第2列不是列名而是一个计算表达式,是用当时的年份(假设为2014年)减去学生的年龄。这样所得的即是学生的出生年份。输出的结果为

Sname 2014-Sage
李勇 1994
刘晨 1995
王敏 1996
张立 1995

表9

  <目标列表达式>不仅可以是算术表达式,还可以是字符串常量、函数等。

  例20:查询全体学生的姓名、出生年份和所在的院系,要求用小写字母表示系名。

1
2
SELECT Sname,'Year of Birth: ',2014-Sage,LOWER(Sdept)
FROM Student;

  结果为

Sname ‘Year of Birth’ 2014-Sage LOWER(Sdept)
李勇 Year of Birth: 1994 cs
刘晨 Year of Birth: 1995 cs
王敏 Year of Birth: 1996 ma
张立 Year of Birth: 1995 is

表10

  用户可以通过指定别名来改变查询结果的列标题,这对于含算术表达式、常量、函数 名的目标列表达式尤为有用。例如对于例20可以定义如下列别名:

1
2
3
SELECT Sname NAME,'Year of Birth:' BIRTH,2014-Sage BIRTHDAY,
       LOWER(Sdept) DEPARTMENT
FROM Student;

  结果为

NAME BIRTH BIRTHDAY DEPARTMENT
李勇 Year of Birth: 1994 cs
刘晨 Year of Birth: 1995 cs
王敏 Year of Birth: 1996 ma
张立 Year of Birth: 1995 is

表11

选择表中的若干元组

1. 消除取值重复的行

  两个本来并不完全相同的元组在投影到指定的某些列上后,可能会变成相同的行。可以用DISTINCT消除它们。

  例21:查询选修了课程的学生学号。

1
2
SELECT Sno
FROM SC;

  执行上面的SELECT语句后,结果为

Sno
201215121
201215121
201215121
201215122
201215122

表12

  该查询结果里包含了许多重复的行。如想去掉结果表中的重复行,必须指定 DISTINCT

1
2
SELECT DISTINCT Sno
FROM SC;

  则执行结果为

Sno
201215121
201215122

表13

  如果没有指定DISTINCT关键词,则默认为ALL,即保留结果表中取值重复的行。

1
2
SELECT Sno
FROM SC;

  等价于

1
2
SELECT ALL Sno
FROM SC;

2. 查询满足条件的元组

  查询满足指定条件的元组可以通过WHERE子句实现。WHERE子句常用的查询条件如表14所示。

查询条件 谓词 示例
比较 =, >, <, >=, <=, !=, <>, !>, !<; NOT+上述比较运算符
确定范围 BETWEEN AND, NOT BETWEEN AND
确定集合 IN, NOT IN
字符匹配 LIKE, NOT LIKE
空值 IS NULL, IS NOT NULL
多重条件(逻辑运算) AND, OR, NOT

表14

  (1)比较大小
  用于进行比较的运算符一般包括=(等于),>(大于),<(小于),>=(大于等于),<=(小于等于),!=<>(不等于),!>(不大于),!<(不小于)。

  例22:查询计算机科学系全体学生的名单。

1
2
3
SELECT Sname
FROM Student
WHERE Sdept='CS';

  关系数据库管理系统执行该查询的一种可能过程是:对Student表进行全表扫描,取 出一个元组,检查该元组在Sdept列的值是否等于CS,如果相等,则取出Sname列的值形成一个新的元组输出;否则跳过该元组,取下一个元组。重复该过程,直到处理完Student表的所有元组。
  如果全校有数万个学生,计算机系的学生人数是全校学生的5%左右,可以在Student表的Sdept列上建立索引,系统会利用该索引找出Sdept='CS'的元组,从中取出Sname列值形成结果关系。这就避免了对Student表的全表扫描,加快了查询速度。注意如果学生较少,索引查找不一定能提高查询效率,系统仍会使用全表扫描。这由查询优化器按照某些规则或估计执行代价来作出选择。

  例23:查询所有年龄在20岁以下的学生姓名及其年龄。

1
2
3
SELECT Sname, Sage
FROM Student
WHERE Sage<20;

  例24:查询考试成绩不及格的学生的学号。

1
2
3
SELECT DISTINCT Sno
FROM SC
WHERE Grade<60;

  这里使用了DISTINCT短语,当一个学生有多门课程不及格,他的学号也只列一次。

  (2)确定范围
  谓词BETWEEN···AND···NOT BETWEEN···AND··可以用来查找找属性值在(或不在)指定范围内的元组,其中BETWEEN后是范围的下限(即低值),AND后是范围的上限(即高值)。

  例25:查询年龄在20〜23岁(包括20岁和23岁)之间的学生的姓名、系别和年龄。

1
2
3
SELECT Sname,Sdept,Sage
FROM Student
WHERE Sage BETWEEN 20 AND 23;

  例26:查询年龄不在20〜23岁之间的学生姓名、系别和年龄。

1
2
3
SELECT Sname,Sdept,Sage
FROM Student
WHERE Sage NOT BETWEEN 20 AND 23;

  (3)确定集合
  谓词IN可以用来查找属性值属于指定集合的元组。

  例27:查询计算机科学系(CS)、数学系(MA)和信息系(IS)学生的姓名和性别。

1
2
3
SELECT Sname,Ssex
FROM Student
WHERE Sdept IN ('CS','MA','IS');

  与IN相对的谓词是NOT IN,用于查找属性值不属于指定集合的元组。

  例28:查询既不是计算机科学系、数学系,也不是信息系的学生的姓名和性别。

1
2
3
SELECT Sname,Ssex
FROM Student
WHERE Sdept NOT IN ('CS','MA','IS');

  (4)字符匹配
  谓词LIKE可以用来进行字符串的匹配。其一般语法格式如下:

1
[NOT] LIKE '<匹配串>' [ESCAPE '换码字符' ]

  其含义是查找指定的属性列值与<匹配串> 相匹配的元组。<匹配串>可以是一个完整的字符串,也可以含有通配符%_。其中:
  ◦%(百分号)代表任意长度(长度可以为0)的字符串。例如a%b表示以a开头,以b结尾的任意长度的字符串。如acbaddgbab等都满足该匹配串。
  ◦_(下横线)代表任意单个字符。例如a_b表示以a开头,以b结尾的长度为3的任意字符串。如acbafb等都满足该匹配串。

  例29:查询学号为201215121的学生的详细情况。

1
2
3
SELECT *
FROM Student
WHERE Sno LIKE '201215121';

  等价于

1
2
3
SELECT *
FROM Student
WHERE Sno='201215121';

  如果LIKE后面的匹配串中不含通配符,则可以用=(等于)运算符取代LIKE谓词,用!=<>(不等于)运算符取代NOT LIKE谓词。

  例30:查询所有姓刘的学生的姓名、学号和性别。

1
2
3
SELECT Sname,Sno,Ssex
FROM Student
WHERE Sname LIKE '刘%'

  例31:查询姓“欧阳”且全名为三个汉字的学生的姓名。

1
2
3
SELECT Sname
FROM Student
WHERE Sname LIKE '欧阳_'

  注意:数据库字符集为ASCII时一个汉字需要两个_;当字符集为GBK时只需要一个_

  例32:查询名字中第二个字为“阳"的学生的姓名和学号。

1
2
3
SELECT Sname, Sno
FROM Student
WHERE Sname LIKE '_阳%';

  例33:查询所有不姓刘的学生的姓名、学号和性别。

1
2
3
SELECT Sname,Sno,Ssex
FROM Student
WHERE Sname NOT LIKE '刘%';

  如果用户要查询的字符串本身就含有通配符%_,这时就要使用ESCAPE '<换码字符>'短语对通配符进行转义了。

  例34:查询DB_Design课程的课程号和学分。

1
2
3
SELECT Cno,Ccredit
FROM Course
WHERE Cname LIKE `DB\_Design` ESCAPE '\';

  ESCAPE '_'表示为“_”为换码字符。这样匹配串中紧跟在后面的字符“_”不再具有通配符的含义,转义为普通的字符。

  例35:查询以'DB_'开头,且倒数第三个字符为'i'的课程的详细情况。

1
2
3
SELECT *
FROM Course
WHERE Cname LIKE 'DB\_%i__' ESCAPE '\';

  这里的匹配串为'DB\_%i__'。第一个_前面有换码字符所以它被转义为普通的_字符。而i后面的两个_的前面均没有换码字符\,所以它们仍作为通配符。

  (5)涉及空值的查询
  例36:某些学生选修课程后没有参加考试,所以有选课记录,但没有考试成绩。查询缺少成绩的学生的学号和相应的课程号。

1
2
3
SELECT Sno,Cno
FROM SC
WHERE Grade IS NULL; /*分数 Grade 是空值*/

  注意这里的IS不能用等号(=)代替。

  例37:查所有有成绩的学生学号和课程号。

1
2
3
SELECT Sno,Cno
FROM SC
WHERE Grade IS NOT NULL;

  (6)多重条件查询
  逻辑运算符ANDOR可用来连接多个查询条件。AND的优先级高于OR,但用户可以用括号改变优先级。

  例38:查询计算机科学系年龄在20岁以下的学生姓名。

1
2
3
SELECT Sname
FROM Student
WHERE Sdept='CS' AND Sage<20;

  在例27中的IN谓词实际上是多个OR运算符的缩写,因此该例中的查询也可以用OR运算符写成如下等价形式:

1
2
3
SELECT Sname,Ssex
FROM Student
WHERE Sdept='CS ' OR Sdept='MA' OR Sdept='IS';

ORDER BY子句

  用户可以用ORDER BY子句对查询结果按照一个或多个属性列的升序(ASC)或降序 (DESC)排列,默认值为升序。

  例39:查询选修了3号课程的学生的学号及其成绩,查询结果按分数的降序排列。

1
2
3
4
SELECT Sno,Grade
FROM SC
WHERE Cno='3'
ORDER BY Grade DESC;

  对于空值,排序时显示的次序由具体系统实现来决定。例如按升序排,含空值的元组最后显示;按降序排,空值的元组则最先显示。各个系统的实现可以不同,只要保持一致就行。

  例40:查询全体学生情况,查询结果按所在系的系号升序排列,同一系中的学生按年龄降序排列。

1
2
3
SELECT *
FROM Student
ORDER BY Sdept,Sage DESC;

聚集函数

  为了进一步方便用户,增强检索功能,SQL提供了许多聚集函数,主要有:

聚集函数 作用
COUNT(*) 统计元组个数
COUNT([DISTINCT|ALL] <列名>) 统计一列中值的个数
SUM([DISTINCT|ALL] <列名>) 计算一列值的总和(此列必须是数值型)
AVG([DISTINCT|ALL] <列名>) 计算一列值的平均值(此列必须是数值型)
MAX([DISTINCT|ALL] <列名>) 求一列值中的最大值
MIN([DISTINCT|ALL] <列名>) 求一列值中的最小值

表15 SQL提供的聚集函数

  如果指定DISTINCT短语,则表示在计算时要取消指定列中的重复值。如果不指定DISTINCT短语或指定ALL短语(ALL为默认值),则表示不取消重复值。

  例41:查询学生总人数。

1
2
SELECT COUNT(*)
FROM Student;

  例42:查询选修了课程的学生人数。

1
2
SELECT COUNT(DISTINCT Sno)
FROM SC;

  学生每选修一门课,在SC中都有一条相应的记录。一个学生要选修多门课程,为避免重复计算学生人数,必须在COUNT函数中用DISTINCT短语。

  例43:计算选修1号课程的学生平均成绩。

1
2
3
SELECT AVG(Grade)
FROM SC
WHERE Cno='1';

  例44:查询选修1号课程的学生最高分数。

1
2
3
SELECT MAX(Grade)
FROM SC
WHERE Cno='1';

  例45:查询学生201215012选修课程的总学分数。

1
2
3
SELECT SUM(Ccredit)
FROM SC,Course
WHERE Sno='201215012' AND SC.Cno=Course.Cno;

  当聚集函数遇到空值时,除COUNT(*)外,都跳过空值而只处理非空值。COUNT(*)是对元组进行计数,某个元组的一个或部分列取空值不影响COUNT的统计结果。
  注意,WHERE子句中是不能用聚集函数作为条件表达式的。聚集函数只能用于SELECT子句和GROUP BY中的HAVING子句。

GROUP BY子句

  GROUP BY子句将查询结果按某一列或多列的值分组,值相等的为一组。
  对查询结果分组的目的是为了细化聚集函数的作用对象。如果未对查询结果分组,聚集函数将作用于整个查询结果,如前面的例41例45。分组后聚集函数将作用于每一个组,即每一组都有一个函数值。

  例46:求各个课程号及相应的选课人数。

1
2
3
SELECT Cno,COUNT(Sno)
FROM SC
GROUP BY Cno;

  该语句对查询结果按Cno的值分组,所有具有相同Cno值的元组为一组,然后对每一组作用聚集函数COUNT进行计算,以求得该组的学生人数。

  查询结果可能为

Cno COUNT(Sno)
1 22
2 34
3 44
4 33
5 48

表16

  如果分组后还要求按一定的条件对这些组进行筛选,最终只输出满足指定条件的组,则可以使用HAVING短语指定筛选条件。

  例47:查询选修了三门以上课程的学生学号。

1
2
3
4
SELECT Sno
FROM SC
GROUP BY Sno
HAVING COUNT(*)>3;

  这里先用GROUP BY子句按Sno进行分组,再用聚集函数COUNT对每一组计数;HAVING短语给出了选择组的条件,只有满足条件(即元组个数>3,表示此学生选修的课超过3门)的组才会被选出来。
  WHERE子句与HAVING短语的区别在于作用对象不同。WHERE子句作用于基本表或视图,从中选择满足条件的元组。HAVING短语作用于组,从中选择满足条件的组。

  例48:查询平均成绩大于等于90分的学生学号和平均成绩。

  下面的语句是不对的:

1
2
3
4
SELECT Sno,AVG(Grade)
FROM SC
WHERE AVG(Grade)>=90
GROUP BY Sno;

  因为WHERE子句中是不能用聚集函数作为条件表达式的,正确的查询语句应该是:

1
2
3
4
SELECT Sno,AVG(Grade)
FROM SC
GROUP BY Sno
HAVING AVG(Grade)>=90;

连接查询

  前面的查询都是针对一个表进行的。若一个查询同时涉及两个以上的表,则称之为连接查询。连接查询是关系数据库中最主要的查询,包括等值连接查询、自然连接查询、非等值连接查询、自身连接查询、外连接查询和复合条件连接查询等。

等值与非等值连接查询

  连接查询的WHERE子句中用来连接两个表的条件称为连接条件或连接谓词,其一般格式为

1
[<表名1>.] <列名1> <比较运算符> [<表名2>.] <列名2>

  其中比较运算符主要有=><>=<=!=(或<>)等。

  此外连接谓词还可以使用下面形式:

1
[<表名 1>.] <列名 1> BETWEEN [<表名 2>.] <列名 2> AND [<表名 2>.] <列名 3>

  当连接运算符为=时,称为等值连接。使用其他运算符称为非等值连接
  连接谓词中的列名称为连接字段。连接条件中的各连接字段类型必须是可比的,但名字不必相同。

  例49:查询每个学生及其选修课程的情况。

  学生情况存放在Student表中,学生选课情况存放在SC表中,所以本查询实际上涉及StudentSC两个表。这两个表之间的联系是通过公共属性Sno实现的。

1
2
3
SELECT Student.*,SC.*
FROM Student,SC
WHERE Student.Sno=SC.Sno; /* 将Student与SC中同一学生的元组连接起来 */

  假设Student表、SC表的数据如表3表5所示,该查询的执行结果如下表所示。

Student.Sno Sname Ssex Sage Sdept SC.Sno Cno Grade
201215121 李勇 20 CS 201215121 1 92
201215121 李勇 20 CS 201215121 2 85
201215121 李勇 20 CS 201215121 3 88
201215122 刘晨 19 CS 201215122 2 90
201215122 刘晨 19 CS 201215122 3 80

表17

  本例中,SELECT子句与WHERE子句中的属性名前都加上了表名前缀,这是为了避免混淆。如果属性名在参加连接的各表中是唯一的,则可以省略表名前缀。
  关系数据库管理系统执行该连接操作的一种可能过程是:首先在表Student中找到第一个元组,然后从头开始扫描SC表,逐一查找与Student第一个元组的Sno相等的SC元组,找到后就将Student中的第一个元组与该元组拼接起来,形成结果表中一个元组。SC全部查找完后,再找Student中第二个元组,然后再从头开始扫描SC,逐一查找满足连接条件的元组,找到后就将Student中的第二个元组与该元组拼接起来,形成结果表中一个元组。重复上述操作,直到Student中的全部元组都处理完毕为止。这就是嵌套循环连接算法的基本思想。如图3所示。

S t u dent S C 学号 S n o 姓名 S nam e 性别 S s e x 年龄 S a g e 所在系 S d e p t 2 0 1 2 1 5 1 2 1 李勇 2 0 C S 2 0 1 2 1 5 1 2 2 刘晨 1 9 C S 2 0 1 2 1 5 1 2 3 王敏 1 8 M A 2 0 1 2 1 5 1 2 5 张立 1 9 I S 学号 S n o 课程号 C n o 成绩 G r a d e 2 0 1 2 1 5 1 2 1 1 92 2 0 1 2 1 5 1 2 1 2 8 5 2 0 1 2 1 5 1 2 1 3 8 8 2 0 1 2 1 5 1 2 2 2 90 2 0 1 2 1 5 1 2 2 3 8 0

图3 关系数据库管理系统执行连接操作的示意图

  如果在SC表Sno上建立了索引的话,就不用每次全表扫描SC表了,而是根据Sno值通过索引找到相应的SC元组。用索引查询SC中满足条件的元组一般会比全表扫描快。

  若在等值连接中把目标列中重复的属性列去掉则为自然连接

  例50:对例49用自然连接完成。

1
2
3
SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade
FROM Student,SC
WHERE Student.Sno=SC.Sno;

  本例中,由于SnameSsexSageSdeptCnoGrade属性列在Student表与SC表中是唯一的,因此引用时可以去掉表名前缀;而Sno在两个表都出现了,因此引用时必须加上表名前缀。
  一条SQL语句可以同时完成选择和连接查询,这时WHERE子句是由连接谓词和选择谓词组成的复合条件。

  例51:查询选修2号课程且成绩在90分以上的所有学生的学号和姓名。

1
2
3
4
SELECT Student.Sno,Sname
FROM Student,SC
WHERE Student.Sno=SC.Sno AND /* 连接谓词 */
SC.Cno='2' AND SC.Grade>90;  /* 其他限定条件 */

  该查询的一种优化(高效)的执行过程是,先从SC中挑选出Cno='2'并且Grade>90的元组形成一个中间关系,再和Student中满足连接条件的元组进行连接得到最终的结果关系。

自身连接

  连接操作不仅可以在两个表之间进行,也可以是一个表与其自己进行连接,称为表的自身连接

  例52:查询每一门课的间接先修课(即先修课的先修课)。

  在Course表中只有每门课的直接先修课信息,而没有先修课的先修课。要得到这个信息,必须先对一门课找到其先修课,再按此先修课的课程号查找它的先修课程。这就要将Course表与其自身连接。
  为此,要为Course表取两个别名,一个是FIRST,另一个是SECOND

Cno C n ame C pno Cc r edit 1 数据库 5 4 2 数学 2 3 信息系统 1 4 4 操作系统 6 3 5 数据结构 7 4 6 数据处理 2 7 P A S C A L语言 6 4 Cno C n ame C pno Cc r edit 1 数据库 5 4 2 数学 2 3 信息系统 1 4 4 操作系统 6 3 5 数据结构 7 4 6 数据处理 2 7 P A S C A L语言 6 4 F IR S T C o u r se S EC O N D C o u r se

图4 Course表的两个别名FIRST和SECOND

  完成该查询的SQL语句为

1
2
3
SELECT FIRST.Cno,SECOND.Cpno
FROM Course FIRST,Course SECOND
WHERE FIRST.Cpno=SECOND.Cno;

  结果为

Cno Cpno
1 7
3 5
5 6

表18

外连接

  在通常的连接操作中,只有满足连接条件的元组才能作为结果输出。如例例49的结果表中没有201215123和201215125两个学生的信息,原因在于他们没有选课,在SC表中没有相应的元组,导致Student中这些元组在连接时被舍弃了。
  有时想以Student表为主体列出每个学生的基本情况及其选课情况。若某个学生没有选课,仍把Student的悬浮元组保存在结果关系中,而在SC表的属性上填空值NULL,这时就需要使用外连接。外连接的概念见关系数据库-外连接。可以参照例53改写例49

  例53

1
2
SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade
FROM Student LEFT OUTER JOIN SC ON (Student.Sno=SC.Sno);

  也可以使用USING来去掉重复值。

1
2
SELECT Student.Sno,Sname,Ssex,Sage,Sdept,Cno,Grade
FROM Student LEFT OUTER JOIN SC USING (Sno);

  执行结果如下。

Student.Sno Sname Ssex Sage Sdept Cno Grade
201215121 李勇 20 CS 1 92
201215121 李勇 20 CS 2 85
201215121 李勇 20 CS 3 88
201215122 刘晨 19 CS 2 90
201215122 刘晨 19 CS 3 80
201215123 刘晨 18 MA NULL NULL
201215125 刘晨 19 IS NULL NULL

表19

  左外连接列出左边关系(如本例Student)中所有的元组,右外连接列出右边关系中所有的元组。

多表连接

  连接操作除了可以是两表连接,一个表与其自身连接外,还可以是两个以上的表进行连接,后者通常称为多表连接

  例54:查询每个学生的学号、姓名、选修的课程名及成绩。

  本查询涉及三个表,完成该查询的SQL语句如下:

1
2
3
SELECT Student.Sno,Sname,Cname,Grade
FROM Student,SC,Course
WHERE Student.Sno=SC.Sno AND SC.Cno=Course.Cno;

  关系数据库管理系统在执行多表连接时,通常是先进行两个表的连接操作,再将其连接结果与第三个表进行连接。本例的一种可能的执行方式是,先将Student表与SC表进行连接,得到每个学生的学号、姓名、所选课程号和相应的成绩,然后再将其与Course表进行连接,得到最终结果。

嵌套查询

  在SQL语言中,一个SELECT-FROM-WHERE语句称为一个查询块。将一个查询块嵌套在另一个查询块的WHERE子句或HAVING短语的条件中的查询称为嵌套查询(nested query)。例如:

1
2
3
4
5
6
7

SELECT Sname
FROM Student       /*外层查询或父查询*/
WHERE Sno IN
    (SELECT Sno    /*内层查询或子查询*/
    FROM SC
    WHERE Cno='2');

  本例中,下层查询块SELECT Sno FROM SC WHERE Cno='2'是嵌套在上层查询块SELECT Sname FROM Student WHERE Sno INWHERE条件中的。上层的查询块称为外层查询父查询,下层查询块称为内层查询子查询
  SQL语言允许多层嵌套查询,即一个子查询中还可以嵌套其他子查询。需要特别指出 的是,子查询的SELECT语句中不能使用ORDER BY子句,ORDER BY子句只能对最终查询结果排序。
  嵌套查询使用户可以用多个简单查询构成复杂的查询,从而增强SQL的查询能力。以层层嵌套的方式来构造程序正是SQL中“结构化”的含义所在。

带有IN谓词的子查询

  在嵌套查询中,子查询的结果往往是一个集合,所以谓词IN是嵌套查询中最经常使用的谓词。

  例55:查询与"刘晨”在同一个系学习的学生。

  先分步来完成此查询,然后再构造嵌套查询。

  ①确定"刘晨”所在系名

1
2
3
SELECT Sdept
FROM Student
WHERE Sname='刘晨';

  结果为CS。

  ②查找所有在CS系学习的学生。

1
2
3
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept='CS';

  结果为

Sno Sname Sdept
201215121 李勇 CS
201215121 刘晨 CS

表20

  将第一步查询嵌入到第二步查询的条件中,构造嵌套查询如下:

1
2
3
4
5
6
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept IN
      (SELECT Sdept
      FROM Student
      WHERE Sname='刘晨');

  本例中,子查询的查询条件不依赖于父查询,称为不相关子查询。一种求解方法是由里向外处理,即先执行子查询,子查询的结果用于建立其父查询的查找条件。得到如下的语句:

1
2
3
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept IN ('CS');

  然后执行该语句。
  本例中的查询也可以用自身连接来完成:

1
2
3
SELECT S1.Sno,S1.Sname,S1.Sdept
FROM Student S1,Student S2
WHERE S1.Sdept=S2.Sdept AND S2.Sname='刘晨';

  可见,实现同一个查询请求可以有多种方法,当然不同的方法其执行效率可能会有差别,甚至会差别很大。这就是数据库编程人员应该掌握的数据库性能调优技术。

  例56:查询选修了课程名为“信息系统”的学生学号和姓名。

  本查询涉及学号、姓名和课程名三个属性。学号和姓名存放在Student表中,课程名存放在Course表中,但StudentCourse两个表之间没有直接联系,必须通过SC表建立它们二者之间的联系。所以本查询实际上涉及三个关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
SELECT Sno,Sname                      /* ③最后在Student关系中 */
FROM Student                          /*  取出Sno和Sname     */
WHERE Sno IN
      (SELECT Sno                     /* ②然后在SC关系中找出  */
      FROM SC                         /*  选修了3号课程的学生  */
      WHERE Cno IN                    /*  学号               */
            (SELECT Cno               /* ①首先在Course关系中  */
             FROM Course              /*  找出"信息系统"的课程 */
             WHERE Cname='信息系统'    /*  号,结果为3号       */
            )
      );

  本查询同样可以用连接查询实现:

1
2
3
4
5
SELECT Student.Sno,Sname
FROM Student,SC,Course
WHERE Student.Sno=SC.Sno AND
      SC.Cno=Course.Cno AND
      Course.Cname='信息系统';

  有些嵌套查询可以用连接运算替代,有些是不能替代的。从例55例56可以看到,查询涉及多个关系时,用嵌套查询逐步求解层次清楚,易于构造,具有结构化程序设计的优点。但是相比于连接运算,目前商用关系数据库管理系统对嵌套查询的优化做得还不够完善,所以在实际应用中,能够用连接运算表达的查询尽可能采用连接运算。
  例55例56中子查询的查询条件不依赖于父查询,这类子查询称为不相关子查询。不相关子查询是较简单的一类子查询。如果子查询的查询条件依赖于父查询,这类子查询称为相关子查询(correlated subquery)‌整个查询语句称为相关嵌套查询(correlated nested query)‌语句。例57就是一个相关子查询的例子。

带有比较运算符的子查询

  带有比较运算符的子查询是指父查询与子查询之间用比较运算符进行连接。当用户能确切知道内层查询返回的是单个值时,可以用><=>=<=!=<>等比较运算符。
  例如在例55中,由于一个学生只可能在一个系学习,也就是说内查询的结果是一个值,因此可以用=代替IN

1
2
3
4
5
6
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept =
      (SELECT Sdept
       FROM Student
       WHERE Sname='刘晨');

  例57:找出每个学生超过他自己选修课程平均成绩的课程号。

1
2
3
4
5
SELECT Sno,Cno
FROM SC x
WHERE Grade>=(SELECT AVG(Grade)
              FROM SC y
              WHERE y.Sno=x.Sno);

  x是表SC的别名,又称为元组变量,可以用来表示SC的一个元组。内层查询是求一个学生所有选修课程平均成绩的,至于是哪个学生的平均成绩要看参数x.Sno的值,而该值是与父查询相关的,因此这类查询称为相关子查询。这个语句的一种可能的执行过程采用以下三个步骤。
  ①从外层查询中取出SC的一个元组x,将元组xSno值(201215121)传送给内层查询。

1
2
3
SELECT AVG(Grade)
FROM SC y
WHERE y.Sno='201215121‘;

  ②执行内层查询,得到AVG值为88(近似值),用该值代替内层查询,得到外层查询:

1
2
3
SELECT Sno, Cno
FROM SC x
WHERE Grade>=88;

  ③执行这个查询,得到

Sno Cno
201215121 1
201215121 3

表21

  然后外层查询取出下一个元组重复做上述①至③步骤的处理,直到外层的SC元组全部处理完毕。结果为

Sno Cno
201215121 1
201215121 3
201215122 2

表22

  求解相关子查询不能像求解不相关子查询那样一次将子查询求解出来,然后求解父查询。内层查询由于与外层查询有关,因此必须反复求值。

带有ANY(SOME)或(ALL)谓词的子查询

子查询返回单值时可以用比较运算符,但返回多值时要用ANY(有的系统用SOME)或ALL谓词修饰符。而使用ANY或ALL谓词时则必须同时使用比较运算符。其语义如下表所示:

运算符 含义
>ANY 大于子查询结果中的某个值
>ALL 大于子查询结果中的所有值
<ANY 小于子查询结果中的某个值
<ALL 小于子查询结果中的所有值
>=ANY 大于等于子查询结果中的某个值
>=ALL 大于等于子查询结果中的所有值
<=ANY 小于等于子查询结果中的某个值
<=ALL 小于等于子查询结果中的所有值
=ANY 等于子查询结果中的某个值
=ALL 等于子查询结果中的所有值(通常没有实际意义)
!=ANY 不等于子查询结果中的某个值
!=ALL 不等于子查询结果中的任何一个值

表23 ANY或ALL运算符与比较运算符组合后的语义

  例58:查询非计算机科学系中比计算机科学系任意一个学生年龄小的学生姓名和年龄。

1
2
3
4
5
6
SELECT Sname,Sage
FROM Student
WHERE Sage<ANY (SELECT Sage
                FROM Student
                WHERE Sdept='CS')
AND Sdept<>'CS';

  结果如下:

Sname Sage
王敏 18
张立 19

表24

  关系数据库管理系统执行此查询时,首先处理子查询,找出CS系中所有学生的年龄,构成一个集合(20,19);然后处理父查询,找所有不是CS系且年龄小于20或19的学生。
  本查询也可以用聚集函数来实现,首先用子查询找出CS系中最大年龄(20),然后在父查询中查所有非CS系且年龄小于20岁的学生。SQL语句如下:

1
2
3
4
5
6
7
SELECT Sname,Sage
FROM Student
WHERE Sage <
    (SELECT MAX(Sage)
    FROM Student
    WHERE SdepU 'CS')
AND Sdept <> 'CS';

  例59:查询非计算机科学系中比计算机科学系所有学生年龄都小的学生姓名及年龄。

1
2
3
4
5
6
7
SELECT Sname,Sage
FROM Student
WHERE Sage < ALL
            (SELECT Sage
            FROM Student
            WHERE Sdept='CS')
AND Sdept<>'CS';'

  关系数据库管理系统执行此查询时,首先处理子查询,找出CS系中所有学生的年龄,构成一个集合(20,19)。然后处理父查询,找所有不是CS系且年龄既小于20,也小于19的学生。查询结果为

Sname Sage
王敏 18

表25

  本查询同样也可以用聚集函数实现。SQL语句如下:

1
2
3
4
5
6
7
SELECT Sname,Sage
FROM Student
WHERE Sage <
            (SELECT MIN(Sage)
            FROM Student
            WHERE Sdept='CS')
AND Sdept<>'CS';

  事实上,用聚集函数实现子查询通常比直接用ANYALL查询效率要高。ANYALL与聚集函数的对应关系如表26所示。

= <>!= < <= > >=
ANY IN -- <MAX <=MAX >MIN >=MIN
ALL -- NOT IN <MIN <=MIN >MAX >=MAX

表26 ANY(或SOME)、ALL谓词与聚集函数,IN谓词的等价转换关系

  表26中,=ANY等价于IN谓词,<ANY等价于<MAX<>ALL等价于NOT IN谓词,<ALL等价于<MIN,等等。

带有EXISTS谓词的子查询

  EXISTS代表存在量词 \(\exists\) 带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值“true”或逻辑假值“false”。
  可以利用EXISTS来判断 \(x \in S\) 、 \(S \sube R\) 、 \(S = R\) 、 \(S \cap R 非空\) 等是否成立。

  例60:查询所有选修了1号课程的学生姓名。

  本查询涉及Student和SC表。可以在Student中依次取每个元组的Sno值,用此值去检查SC表。若SC中存在这样的元组,其Sno值等于此Student.Sno值,并且其Cno='1',则取此Student.Sname送入结果表。将此想法写成SQL语句是

1
2
3
4
5
6
SELECT Sname
FROM Student
WHERE EXISTS
      (SELECT *
      FROM SC
      WHERE Sno=Student.Sno AND Cno='1');

  使用存在量词EXISTS后,若内层查询结果非空,则外层的WHERE子句返回真值,否则返回假值。
  由EXISTS引出的子查询,其目标列表达式通常都用*,因为带EXISTS的子查询只返回真值或假值,给出列名无实际意义。
  本例中子查询的查询条件依赖于外层父查询的某个属性值(StudentSno值),因此也是相关子查询。这个相关子查询的处理过程是:首先取外层查询中Student表的第一个元组,根据它与内层查询相关的属性值(Sno值)处理内层查询,若WHERE子句返回值为真,则取外层查询中该元组的Sname放入结果表:然后再取Student表的下一个元组; 重复这一过程,直至外层Student表全部检查完为止。 本例中的查询也可以用连接运算来实现,读者可以参照有关的例子自己给出相应的SQL语句。

  与EXISTS谓词相对应的是NOT EXISTS谓词。使用存在量词NOT EXISTS后,若内层查询结果为空,则外层的WHERE子句返回真值,否则返回假值。

  例61:查询没有选修1号课程的学生姓名。

1
2
3
4
5
6
SELECT Sname
FROM Student
WHERE NOT EXISTS
        (SELECT *
        FROM SC
        WHERE Sno=Student.Sno AND Cno='1');

  一些带EXISTSNOT EXISTS谓词的子查询不能被其他形式的子查询等价替换,但所有带IN谓词、比较运算符、ANYALL谓词的子查询都能用带EXISTS谓词的子查询等价替换。例如带有IN谓词的例55可以用如下带EXISTS谓词的子查询替换:

1
2
3
4
5
6
7
SELECT Sno,Sname,Sdept
FROM Student SI
WHERE EXISTS
      (SELECT *
      FROM Student S2
      WHERE S2.Sdept=S1.Sdept AND
      S2.Sname='刘晨');

  由于带EXISTS量词的相关子查询只关心内层查询是否有返回值,并不需要查具体值,因此其效率并不一定低于不相关子查询,有时是高效的方法。

  例62:查询选修了全部课程的学生姓名。

  SQL中没有全称量词(for all),但是可以把带有全称量词的谓词转换为等价的带有存在量词的谓词:

\[ (\forall x)P \equiv \lnot (\exists x( \lnot P)) \tag{1} \]

   \(\lnot (\exists x( \lnot P))\) 由于没有全称量词,可将题目的意思转换成等价的用存在量词的形式:查询这样的学生,没有一门课程是他不选修的。其SQL语句如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
SELECT Sname
FROM Student
WHERE NOT EXISTS
      (SELECT *
      FROM Course
      WHERE NOT EXISTS
            (SELECT *
             FROM SC
             WHERE Sno=Student.Sno
               AND Cno=Course.Cno));

  从而用EXIST/NOT EXIST来实现带全称量词的查询。

  例63:查询至少选修了学生201215122选修的全部课程的学生号码。

  本查询可以用逻辑蕴涵来表达:查询学号为 \(x\) 的学生,对所有的课程 \(y\) ,只要201215122学生选修了课程y,则x也选修了y。形式化表示如下:
  用 \(p\) 表示谓词“学生201215122选修了课程 \(y\) ”
  用 \(q\) 表示谓词“学生 \(x\) 选修了课程 \(y\) ”
  则上述查询为

\[ (\forall y)p \to q \tag{2} \]

  SQL语言中没有蕴涵(implication)逻辑运算,但是可以利用谓词演算将一个逻辑蕴涵的谓词等价转换为

\[ p \to q \equiv \lnot p \lor q \tag{3} \]

  由式(1)式(3)该查询可以转换为如下等价形式:

\[ (\forall y)p \to q \equiv \lnot (\exists x( \lnot (p \to q))) \equiv \lnot (\exists x( \lnot (\lnot p \lor q))) \equiv \lnot \exists y(p \land \lnot q) \tag{4} \]

  它所表达的语义为:不存在这样的课程y,学生201215122选修了y,而学生x没有选。用SQL语言表示如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
SELECT DISTINCT Sno
FROM SC SCX
WHERE NOT EXISTS
      (SELECT *
      FROM SC SCY
      WHERE SCY.Sno='201215122' AND
            NOT EXISTS
            (SELECT *
            FROM SC SCZ
            WHERE SCZ.Sno=SCX.Sno AND
                  SCZ.Cno=SCY.Cno));

集合查询

  SELECT语句的查询结果是元组的集合,所以多个SELECT语句的结果可进行集合操作。集合操作主要包括并操作UNION,交操作INTERSECT和差操作EXCEPT
  注意,参加集合操作的各查询结果的列数必须相同;对应项的数据类型也必须相同。

  例64:查询计算机科学系的学生及年龄不大于19岁的学生。

1
2
3
4
5
6
7
SELECT *
FROM Student
WHERE Sdept='CS'
UNION
SELECT *
FROM Student
WHERE Sage<=19;

  本查询实际上是求计算机科学系的所有学生与年龄不大于19岁的学生的并集。使用UNION将多个查询结果合并起来时,系统会自动去掉重复元组。如果要保留重复元组则用UNION ALL操作符。

  例65:查询选修了课程1或者选修了课程2的学生。

  本例即查询选修课程1的学生集合与选修课程2的学生集合的并集。

1
2
3
4
5
6
7
SELECT Sno
FROM SC
WHERE Cno= '1'
UNION
SELECT Sno
FROM SC
WHERE Cno='2';

  例66:查询计算机科学系的学生与年龄不大于19岁的学生的交集。

1
2
3
4
5
6
7
SELECT *
FROM Student
WHERE Sdept='CS'
INTERSECT
SELECT *
FROM Student
WHERE Sage<=19;

  这实际上就是查询计算机科学系中年龄不大于19岁的学生。

1
2
3
4
SELECT *
FROM Student
WHERE Sdept='CS' AND
      Sage<=19;

  例67:查询既选修了课程1又选修了课程2的学生。就是查询选修课程1的学生 集合与选修课程2的学生集合的交集。

1
2
3
4
5
6
7
SELECT Sno
FROM SC
WHERE Cno='1'
INTERSECT
SELECT Sno
FROM SC
WHERE Cno='2';

  本例也可以表示为

1
2
3
4
5
6
SELECT Sno
FROM SC
WHERE Cno='1' AND Sno IN
                 (SELECT Sno
                  FROM SC
                  WHERE Cno='2');

  例68:查询计算机科学系的学生与年龄不大于19岁的学生的差集。

1
2
3
4
5
6
7
SELECT *
FROM Student
WHERE Sdept='CS'
EXCEPT
SELECT *
FROM Student
WHERE Sage <=19;

  也就是查询计算机科学系中年龄大于19岁的学生。

1
2
3
SELECT *
FROM Student
WHERE Sdept='CS' AND Sage>19;

基于派生表的查询

  子查询不仅可以出现在WHERE子句中,还可以出现在FROM子句中,这时子查询生成的临时派生表(derived table)成为主查询的查询对象。例如,例57找出每个学生超过他自己选修课程平均成绩的课程号,也可以用如下的查询完成:

1
2
3
4
SELECT Sno, Cno
FROM SC, (SELECT Sno, Avg(Grade) FROM SC GROUP BY Sno)
          AS Avg_sc(avg_sno,avg_grade)
WHERE SC.Sno = Avg_sc.avg_sno and SC.Grade >= Avg_sc.avg_grade

  这里FROM子句中的子查询将生成一个派生表Avg_sc。该表由avg_snoavg_grade两个属性组成,记录了每个学生的学号及平均成绩。主查询将SC表与Avg_sc按学号相等进行连接,选出修课成绩大于其平均成绩的课程号。 如果子查询中没有聚集函数,派生表可以不指定属性列,子查询SELECT子句后面的列 名为其默认属性。例如例60查询所有选修了1号课程的学生姓名,可以用如下查询完成:

1
2
3
SELECT Sname
FROM Student, (SELECT Sno FROM SC WHERE Cno='1') AS SC1
WHERE Student.Sno=SC1.Sno;

  需要说明的是,通过FROM子句生成派生表时,AS关键字可以省略,但必须为派生关系指定一个别名。而对于基本表,别名是可选择项。

SELECT语句的一般格式

  SELECT语句是SQL的核心语句,从前面的例子可以看到其语句成分丰富多样,下面总结一下它们的一般格式。
  SELECT语句的一般格式:

1
2
3
4
5
SELECT [ALL|DISTINCT] <目标列表达式> [别名][,<目标列表达式> [别名]]···
FROM <表名或视图名> [别名][,<表名或视图名> [别名]]···| (<SELECT语句>)[AS] <别名>
[WHERE <条件表达式>]
[GROUP BY <列名 1> [HAVING <条件表达式>]]
[ORDER BY <列名 2> [ASC|DESC]];

目标列表达式的可选格式

  (1)*
  (2)<表名>.*
  (3)COUNT([DISTINCT|ALL] *)
  (4)<表名>.] <属性列名表达式> [,[<表名>.] <属性列名表达式>]···

  其中,<属性列名表达式>可以是由属性列、作用于属性列的聚集函数和常量的任意算术运算(+-*/)组成的运算公式。

聚集函数的一般格式

  \(\displaystyle \begin{Bmatrix} \textsf{COUNT} \\[5pt] \textsf{SUM} \\[5pt] \textsf{AVG} \\[5pt] \textsf{MAX} \\[5pt] \textsf{MIN} \end{Bmatrix}\space \textsf{[DISTINCT|ALL] }\text{<列名>} \)

WHERE子句的条件表达式的可选格式

  (1)

  \( \text{<属性列名>}\space\theta\space \begin{Bmatrix} \text{<属性列名>}\\[5pt] \text{<常量>}\\[5pt] [\textsf{ANY}|\textsf{ALL}]\space(\textsf{SELECT}\text{子句}) \end{Bmatrix} \)

  (2)

  \( \text{<属性列名>}\space[\textsf{NOT}]\space\textsf{BETWEEN}\space \begin{Bmatrix} \text{<属性列名>}\\[5pt] \text{<常量>}\\[5pt] (\textsf{SELECT}\text{语句}) \end{Bmatrix} \space\textsf{AND}\space \begin{Bmatrix} \text{<属性列名>}\\[5pt] \text{<常量>}\\[5pt] (\textsf{SELECT}\text{语句}) \end{Bmatrix} \)

  (3)

  \( \text{<属性列名>}\space[\textsf{NOT}]\space\textsf{IN}\space \begin{Bmatrix} (\text{<值1>}[,\text{<值2>}]\cdots)\\[5pt] (\textsf{SELECT}\text{语句}) \end{Bmatrix} \)

  (4)<属性列名> [NOT] LIKE <匹配串>
  (5)<属性列名> IS [NOT] NULL
  (6)[NOT] EXISTS(SELECT语句)
  (7)

  \( \text{<条件表达式>}\space \begin{Bmatrix} \textsf{AND}\\[5pt] \textsf{OR} \end{Bmatrix} \space\text{<条件表达式>}\space \left( \begin{Bmatrix} \textsf{AND}\\[5pt] \textsf{OR} \end{Bmatrix} \text{<条件表达式>}\cdots \right) \)

数据更新

  数据更新操作有三种:向表中添加若干行数据、修改表中的数据和删除表中的若干行数据。在SQL中有相应的三类语句。

插入数据

  SQL的数据插入语句INSERT通常有两种形式,一种是插入一个元组,另一种是插入子查询结果。后者可以一次插入多个元组。

插入元组

  插入元组的INSERT语句的格式为

1
2
3
INSERT
INTO <表名> [(<属性列1> [,<属性列2>] ···)]
VALUES (<常量 1> [,常量 2>]···);

  其功能是将新元组插入指定表中。其中新元组的属性列1的值为常量1,属性列2的值为常量2,···。INTO子句中没有出现的属性列,新元组在这些列上将取空值。但必须注意的是,在表定义时说明了NOT NULL的属性列不能取空值,否则会出错。
  如果INTO子句中没有指明任何属性列名,则新插入的元组必须在每个属性列上均有值。

  例69:将一个新学生元组(学号:201215128;姓名:陈冬;性别:男;所在系: IS;年龄:18岁)插入到Student表中。

1
2
3
INSERT
INTO Student (Sno,Sname,Ssex,Sdept,Sage
VALUES ('201215128','陈冬','男','IS', 18);

  在INTO子句中指出了表名Student,并指出了新增加的元组在哪些属性上要赋值,属性的顺序可以与CREATE TABLE中的顺序不一样。VALUES子句对新元组的各属性赋值,字符串常数要用单引号(英文符号)括起来。

  例70:将学生张成民的信息插入到Student表中。

1
2
3
INSERT
INTO Student
VALUES ('201215126','张成民','男',18,'CS');

  与例69的不同是在INTO子句中只指出了表名,没有指出属性名。这表示新元组要 在表的所有属性列上都指定值,属性列的次序与CREATE TABLE中的次序相同。VALUES 子句对新元组的各属性列赋值,一定要注意值与属性列要一一对应,如果像例69那样,成为(201215126’,‘张成民,’,男,’,CS,18),则含义是将CS赋予了列Sage,而18赋予了列Sdept,这样则会因为数据类型不匹配出错。

  例71例72:插入一条选课记录('201215128','1')

1
2
3
INSERT
INTO SC(Sno,Cno)
VALUES ('201215128','1');

  关系数据库管理系统将在新插入记录的Grade列上自动地赋空值。 或者:

1
2
3
INSERT
INTO SC
VALUES ('201215128','1',NULL);

  因为没有指出SC的属性名,在Grade列上要明确给出空值。

插入子查询结果

  子查询不仅可以嵌套在SELECT语句中用以构造父查询的条件, 也可以嵌套在INSERT语句中用以生成要插入的批量数据。
  插入子查询结果的INSERT语句格式为

1
2
3
INSERT
INTO <表名> [(<属性列1> [,<属性列2>···])]
子查询

  例73:对每一个系,求学生的平均年龄,并把结果存入数据库。

  首先在数据库中建立一个新表,其中一列存放系名,另一列存放相应的学生平均年龄。

1
2
3
CREATE TABLE Dept_age
       (Sdept CHAR(15)
        Avg_age SMALLINT);

  然后对Student表按系分组求平均年龄,再把系名和平均年龄存入新表中。

1
2
3
4
5
INSERT
INTO Dept_age(Sdept,Avg_age)
SELECT Sdept,AVG(Sage)
FROM Student
GROUP BY Sdept;

修改数据

  修改操作又称为更新操作,其语句的一般格式为

1
2
3
UPDATE <表名>
SET <列名>=<表达式> [,<列名>=<表达式>]···
[WHERE <条件>];

  其功能是修改指定表中满足WHERE子句条件的元组。其中SET子句给出<表达式>的值 用于取代相应的属性列值。如果省略WHERE子句,则表示要修改表中的所有元组。

修改某一个元组的值

  例74:将学生201215121的年龄改为22岁。

1
2
3
UPDATE Student
SET Sage=22
WHERE Sno='201215121';

修改多个元组的值

  例75:将所有学生的年龄增加1岁。

1
2
UPDATE Student
SET Sage=Sage+1;

带子查询的修改语句

  子查询也可以嵌套在UPDATE语句中,用以构造修改的条件。

  例76:将计算机科学系全体学生的成绩置零。

1
2
3
4
5
6
UPDATE SC
SET Grade=O
WHERE Sno IN
      (SELETE Sno
       FROM Student
       WHERE Sdept='CS');

删除数据

  删除语句的一般格式为

1
2
3
DELETE
FROM <表名>
[WHERE <条件>];

  DELETE语句的功能是从指定表中删除满足WHERE子句条件的所有元组。如果省略WHERE子句则表示删除表中全部元组,但表的定义仍在字典中。也就是说,DELETE语句删除的是表中的数据,而不是关于表的定义。

删除某一个元组的值

  例77:删除学号为201215128的学生记录。

1
2
3
DELETE
FROM Student
WHERE Sno='201215128';

删除多个元组的值

  删除所有的学生选课记录。

1
2
DELETE
FROM SC;

  这条DELETE语句将使SC成为空表,它删除了SC的所有元组。

带子查询的删除语句

  子查询同样也可以嵌套在DELETE语句中,用以构造执行删除操作的条件。

  例78:删除计算机科学系所有学生的选课记录。

1
2
3
4
5
6
DELETE
FROM SC
WHERE Sno IN
      (SELECT Sno
       FROM Student
       WHERE Sdept='CS');

空值的处理

  前面己经多处提到空值(NULL)的概念和空值的处理,这里再系统介绍一下这个问题。所谓空值就是“不知道”或“不存在”或"无意义”的值。SQL语言中允许某些元组 的某些属性在一定情况下取空值。一般有以下几种情况:
  ◦该属性应该有一个值,但目前不知道它的具体值。例如,某学生的年龄属性,因为 学生登记表漏填了,不知道该学生年龄,因此取空值。
  ◦该属性不应该有值。例如,缺考学生的成绩为空,因为他没有参加考试。
  ◦由于某种原因不便于填写。例如,一个人的电话号码不想让大家知道,则取空值。
  因此,空值是一个很特殊的值,含有不确定性,对关系运算带来特殊的问题,需要做特殊的处理。

空值的产生

  例79:向SC表中插入一个元组,学生号是“201215126”,课程号是"1”,成绩为空。

1
2
3
INSERT INTO SC(Sno,Cno,Grade)
    VALUES('201215126', '1', NULL);
/*在插入时该学生还没有考试成绩,取空值*/

  或

1
2
3
INSERT INTO SC(Sno.Cno)
    VALUES(1201215126','1');
/*在插入语句中没有赋值的属性,其值为空值*/

  例80:将Student表中学生号为“201215200”的学生所属的系改为空值。

1
2
3
UPDATE Student
SET Sdept = NULL
WHERE Sno='201215200';

  另外,外连接也会产生空值,参见外连接。空值的关系运算也会产生空值。

空值的判断

  判断一个属性的值是否为空值,用IS NULLIS NOT NULL来表示。

  例81:从Student表中找出漏填了数据的学生信息。

1
2
3
SELECT *
FROM Student
WHERE Sname IS NULL OR Ssex IS NULL OR Sage IS NULL OR Sdept IS NULL;

空值的约束条件

  属性定义(或者域定义)中有NOT NULL约束条件的不能取空值,加了 UNIQUE限制的属性不能取空值,码属性不能取空值。

空值的算术运算,比较运算和逻辑运算

  空值与另一个值(包括另一个空值)的算术运算的结果为空值,空值与另一个值(包括另一个空值)的比较运算的结果为UNKNOWN。有了UNKNOWN后,传统的逻辑运算中二值(TRUE, FALSE)逻辑就扩展成了三值逻辑。
  AND、OR、NOT的真值表如表27所示,其中T表示TRUE, F表示FALSE,U表示UNKNOWN
  在查询语句中,只有使WHEREHAVING子句中的选择条件为TRUE的元组才被选出作为输出结果。

x y x AND y x OR y NOT x
T T T T F
T U U T F
T F F T F
U T U T U
U U U U U
U F F U U
F T F T T
F U F U T
F F F F T

表27 扩展后的逻辑运算符真值表

  在查询语句中,只有使WHEREHAVING子句中的选择条件为TRUE的元组才被选出作为输出结果。

  例82:找出选修1号课程的不及格的学生。

1
2
3
SELECT Sno
FROM SC
WHERE Grade < 60 AND Cno='1';

  选出的学生是那些参加了考试(Grade属性为非空值)而不及格的学生,不包括缺考的学生。因为前者使条件Grade<60的值为TRUE,后者使条件的值为UNKNOWN

  例83:选出选修1号课程的不及格的学生以及缺考的学生。

1
2
3
4
5
6
7
SELECT Sno
FROM SC
WHERE Grade < 60 AND Cno='1'
UNION
SELECT Sno
FROM SC
WHERE Grade IS NULL AND Cno='1';

  或

1
2
3
SELECT Sno
FROM SC
WHERE Cno=T AND (Grade < 60 OR Grade IS NULL);

视图

  视图是从一个或几个基本表(或视图)导出的表。它与基本表不同,是一个虚表。数据库中只存放视图的定义,而不存放视图对应的数据,这些数据仍存放在原来的基本表中。所以一旦基本表中的数据发生变化,从视图中查询出的数据也就随之改变了。从这个意义上讲,视图就像一个窗口,透过它可以看到数据库中自己感兴趣的数据及其变化。
  视图一经定义,就可以和基本表一样被查询、被删除。也可以在一个视图之上再定义新的视图,但对视图的更新(增、删、改)操作则有一定的限制。
  本节专门讨论视图的定义、操作及作用。

定义视图

建立视图

  SQL语言用CREATE VIEW命令建立视图,其一般格式为

1
2
3
CREATE VIEW <视图名> [(<列名> [,<列名>]···)]
AS <子查询>
[WITH CHECK OPTION];

  其中,子查询可以是任意的SELECT语句,是否可以含有ORDER BY子句和DISTINCT短语,则取决于具体系统的实现。
  WITH CHECK OPTION表示对视图进行UPDATEINSERTDELETE操作时要保证更新、插入或删除的行满足视图定义中的谓词条件(即子查询中的条件表达式)。
  组成视图的属性列名或者全部省略或者全部指定,没有第三种选择。如果省略了视图的各个属性列名,则隐含该视图由子查询中SELECT子句目标列中的诸字段组成但在下列三种情况下必须明确指定组成视图的所有列名:
  (1)某个目标列不是单纯的属性名,而是聚集函数或列表达式。
  (2)多表连接时选出了几个同名列作为视图的字段。
  (3)需要在视图中为某个列启用新的更合适的名字。

  例84:建立信息系学生的视图。

1
2
3
4
5
CREATE VIEW IS_Student
AS
SELECT Sno,Sname,Sage
FROM Student
WHERE Sdept='IS';

  本例中省略了视图IS_Student的列名,隐含了由子查询中SELECT子句中的三个列名组成。
  关系数据库管理系统执行CREATE VIEW语句的结果只是把视图的定义存入数据字典,并不执行其中的SELECT语句。只是在对视图查询时,才按视图的定义从基本表中将数据查出。

  例85:建立信息系学生的视图,并要求进行修改和插入操作时仍需保证该视图只有信息系的学生。

1
2
3
4
5
6
CREATE VIEW IS_Student
AS
SELECT Sno,Sname,Sage
FROM Student
WHERE Sdept='IS'
WITH CHECK OPTION;

  由于在定义IS_Student视图时加上了WITH CHECK OPTION子句,以后对该视图进行插入、修改和删除操作时,关系数据库管理系统会自动加上Sdept=IS的条件。
  若一个视图是从单个基本表导出的,并且只是去掉了基本表的某些行和某些列,但保留了主码,则称这类视图为行列子集视图IS_Student视图就是一个行列子集视图。
  视图不仅可以建立在单个基本表上,也可以建立在多个基本表上。

  例86:建立信息系选修了1号课程的学生的视图(包括学号、姓名、成绩)。

1
2
3
4
5
6
7
CREATE VIEW IS_S1(Sno,Sname,Grade)
AS
SELECT Student.Sno,Sname,Grade
FROM Student,SC
WHERE Sdept='IS' AND
      Student.Sno=SC.Sno AND
      SC.Cno='1';

  由于视图IS_S1的属性列中包含了Student表与SC表的同名列Sno,所以必须在视图名后面明确说明视图的各个属性列名。
  视图不仅可以建立在一个或多个基本表上,也可以建立在一个或多个己定义好的视图上,或建立在基本表与视图上。

  例87:建立信息系选修了1号课程且成绩在90分以上的学生的视图。

1
2
3
4
5
CREATE VIEW IS_S2
AS
SELECT Sno,Sname,Grade
FROM IS_S1
WHERE Grade>=90;

  这里的视图IS_S2就是建立在视图IS_S1之上的。
  定义基本表时,为了减少数据库中的冗余数据,表中只存放基本数据,由基本数据经过各种计算派生出的数据一般是不存储的。由于视图中的数据并不实际存储,所以定义视图时可以根据应用的需要设置一些派生属性列。这些派生属性由于在基本表中并不实际存在,也称它们为虚拟列。带虚拟列的视图也称为带表达式的视图。

  例88:定义一个反映学生出生年份的视图。

1
2
3
CREATE VIEW BT_S(Sno,Sname,Sbirth)
SELECT Sno,Sname,2014-Sage
FROM Student;

  这里视图BT_S是一个带表达式的视图。视图中的出生年份值是通过计算得到的。
  还可以用带有聚集函数和GROUP BY子句的查询来定义视图,这种视图称为分组视图。

  例89:将学生的学号及平均成绩定义为一个视图。

1
2
3
4
5
CREATE VIEW S_G(Sno,Gavg)
AS
SELECT Sno,AVG(Grade)
FROM SC
GROUP BY Sno;

  由于AS子句中SELECT语句的目标列平均成绩是通过作用聚集函数得到的,所以CREATE VIEW中必须明确定义组成S_G视图的各个属性列名。S_G是一个分组视图。

  例90:将Student表中所有女生记录定义为一个视图。

1
2
3
4
5
CREATE VIEW F_Student(F_sno,name,sex,age,dept)
AS
SELECT *
FROM Student
WHERE Ssex='女';

  这里视图F_Student是由子查询“SELECT *”建立的。F_Student视图的属性列与Student表的属性列一一对应。如果以后修改了基本表Student的结构,则Student表与F_Student视图的映像关系就会被破坏,该视图就不能正常工作了。为避免出现这类问题,最好在修改基本表之后删除由该基本表导出的视图,然后重建这个视图。

删除视图

  该语句的格式为

1
DROP VIEW <视图名> [CASCADE];

  视图删除后视图的定义将从数据字典中删除。如果该视图上还导出了其他视图,则使用CASCADE级联删除语句把该视图和由它导出的所有视图一起删除。
  基本表删除后,由该基本表导出的所有视图均无法使用了,但是视图的定义没有从字典中清除。删除这些视图定义需要显式地使用DROP VIEW语句。

  例91:删除视图BT_S和视图IS_SI

1
2
DROP VIEW BT_S;  /*成功执行*/
DROP VIEW IS_S1; /*拒绝执行*/

  执行此语句时由于1S_S1视图上还导出了IS_S2视图,所以该语句被拒绝执行。如果确定要删除,则使用级联删除语句:

1
DROPVIEWIS_S1 CASCADE; /*删除了视图IS_S1和由它导出的所有视图*/

查询视图

  视图定义后,用户就可以像对基本表一样对视图进行查询了。

  例92:在信息系学生的视图中找出年龄小于20岁的学生。

1
2
3
SELECT Sno,Sage
FROM IS_Student
WHERE Sage<20;

  关系数据库管理系统执行对视图的查询时,首先进行有效性检查,检查查询中涉及的表、视图等是否存在。如果存在,则从数据字典中取出视图的定义,把定义中的子查询和用户的查询结合起来,转换成等价的对基本表的查询,然后再执行修正了的查询。这一转换过程称为视图消解(view resolution)
  本例转换后的查询语句为

1
2
3
SELECT Sno,Sage
FROM Student
WHERE Sdept='IS' AND Sage<20;

  例93:查询选修了 1号课程的信息系学生。

1
2
3
SELECT IS_Student.Sno,Sname
FROM IS_Student,SC
WHERE IS_Student.Sno=SC.Sno AND SC.Cno='1';

  本查询涉及视图IS_Student(虚表)和基本表SC,通过这两个表的连接来完成用户请求。在一般情况下,视图查询的转换是直截了当的。但有些情况下,这种转换不能直接进行,查询时就会出现问题,如例94

  例94:在S_G视图(例89中定义的视图)中查询平均成绩在90分以上的学生学号和平均成绩,语句为

1
2
3
SELECT *
FROM S_G
WHERE Gavg>=90;

  例3.89中定义S_G视图的子查询为

1
2
3
SELECT Sno,AVG(Grade)
FROM SC
GROUP BY Sno;

  将本例中的查询语句与定义S_G视图的子查询结合,形成下列查询语句:

1
2
3
4
SELECT Sno,AVG(Grade)
FROM SC
WHERE AVG(Grade)>=90
GROUP BY Sno;

  因为WHERE子句中是不能用聚集函数作为条件表达式的,因此执行此修正后的查询将会出现语法错误。正确转换的查询语句应该是

1
2
3
4
SELECT Sno,AVG(Grade)
FROM SC
GROUP BY Sno
HAVING AVG(Grade)>=90;

  目前多数关系数据库系统对行列子集视图的查询均能进行正确转换。但对非行列子集视图的查询(如例94)就不一定能做转换了,因此这类查询应该直接对基本表进行。

  例94也可以用如下SQL语句完成:

1
2
3
4
5
SELECT *
FROM (SELECT Sno,AVG(Grade)   /*子查询生成一个派生表 S_G*/
FROM SC
GROUP BY Sno) AS S_G(Sno, Gavg)
WHERE Gavg>=90;

  但定义视图并查询视图与基于派生表的查询是有区别的。视图一旦定义,其定义将永久保存在数据字典中,之后的所有查询都可以直接引用该视图。而派生表只是在语句执行时临时定义,语句执行后该定义即被删除。

更新视图

  更新视图是指通过视图来插入(INSERT)、删除(DELETE)和修改(UPDATE)数据。
  由于视图是不实际存储数据的虚表,因此对视图的更新最终要转换为对基本表的更新。像查询视图那样,对视图的更新操作也是通过视图消解,转换为对基本表的更新操作。为防止用户通过视图对数据进行增加、删除、修改时,有意无意地对不属于视图范围内的基本表数据进行操作,可在定义视图时加上WITH CHECK OPTION子句。这样在视图上增、删、改数据时,关系数据库管理系统会检查视图定义中的条件,若不满足条件则 拒绝执行该操作。

  例95:将信息系学生视图IS_Student中学号为“201215122”的学生姓名改为"刘辰”。

1
2
3
UPDATE IS_Student
SET Sname='刘辰'
WHERE Sno='201215122';

  转换后的更新语句为

1
2
3
UPDATE Student
SET Sname='刘辰'
WHERE Sno='201215122' AND Sdept='IS';

  例96:向信息系学生视图IS_Student中插入一个新的学生记录,其中学号为 “201215129”,姓名为“赵新”,年龄为20岁。

1
2
3
INSERT
INTO IS_Student
VALUES ('201215129','赵新','20');

  转换为对基本表的更新:

1
2
3
INSERT
INTO Student (Sno,Sname,Sage,Sdept)
VALUES('201215129','赵新',20,'IS');

  这里系统自动将系名'IS'放入VALUES子句中。

  例97:删除信息系学生视图IS_Student中学号为"201215129"的记录。

1
2
3
DELETE
FROM IS Student
WHERE Sno='201215129';

  转换为对基本表的更新:

1
2
3
DELETE
FROM Student
WHERE Sno='201215129' AND Sdept='IS';

  在关系数据库中,并不是所有的视图都是可更新的,因为有些视图的更新不能唯一地有意义地转换成对相应基本表的更新。
  例如,例89定义的视图S_G是由学号和平均成绩两个属性列组成的,其中平均成绩一项是由Student表中对元组分组后计算平均值得来的。

1
2
3
4
5
CREATE VIEW S_G (Sno,Gavg)
AS
SELECT Sno,AVG (Grade)
FROM SC
GROUP BY Sno;

  如果想把视图S_G中学号为“201215121”的学生的平均成绩改成90分,SQL语句如下:

1
2
3
UPDATE S_G
SET Gavg=90
WHERE Sno='20121512';

  但这个对视图的更新是无法转换成对基本表SC的更新的,因为系统无法修改各科成绩,以使平均成绩成为90。所以S_G视图是不可更新的。
  一般地,行列子集视图是可更新的。除行列子集视图外,有些视图理论上是可更新的,但它们的确切特征还是尚待研究的课题。还有些视图从理论上就是不可更新的。
  目前,各个关系数据库管理系统一般都只允许对行列子集视图进行更新,而且各个系统对视图的更新还有更进一步的规定。由于各系统实现方法上的差异,这些规定也不尽相同。
  例如,DB2规定:
  (1)若视图是由两个以上基本表导出的,则此视图不允许更新。
  (2)若视图的字段来自字段表达式或常数,则不允许对此视图执行INSERTUPDATE操作,但允许执行DELETE操作。
  (3)若视图的字段来自聚集函数,则此视图不允许更新。
  (4)若视图定义中含有GROUP BY子句,则此视图不允许更新。
  (5)若视图定义中含有DISTINCT短语,则此视图不允许更新。
  (6)若视图定义中有嵌套查询,并且内层查询的FROM子句中涉及的表也是导出该视图的基本表,则此视图不允许更新。例如,将SC表中成绩在平均成绩之上的元组定义成一个视图GOOD_SC

1
2
3
4
5
6
7
CREATE VIEW GOOD SC
AS
SELECT Sno,Cno,Grade
FROM SC
WHERE Grade >
            (SELECT AVG(Grade)
             FROM SC);

  导出视图GOOD_SC的基本表是SC,内层查询中涉及的表也是SC,所以视图GOOD_SC是不允许更新的。
  (7)一个不允许更新的视图上定义的视图也不允许更新。
  应该指出的是,不可更新的视图与不允许更新的视图是两个不同的概念。前者指理论上已证明其是不可更新的视图。后者指实际系统中不支持其更新,但它本身有可能是可更新的视图。

视图的作用

  视图最终是定义在基本表之上的,对视图的一切操作最终也要转换为对基本表的操作。而且对于非行列子集视图进行查询或更新时还有可能出现问题。既然如此,为什么还要定义视图呢?这是因为合理使用视图能够带来许多好处。

视图能够简化用户的操作

  视图机制使用户可以将注意力集中在所关心的数据上。如果这些数据不是直接来自基本表,则可以通过定义视图使数据库看起来结构简单、清晰,并且可以简化用户的数据查询操作。 例如,那些定义了若干张表连接的视图就将表与表之间的连接操作对用户隐蔽起来了。换句话说,用户所做的只是对一个虚表的简单查询,而这个虚表是怎样得来的,用户无须了解。

视图使用户能以多种角度看待同一数据

  视图机制能使不同的用户以不同的方式看待同一数据,当许多不同种类的用户共享同一个数据库时,这种灵活性是非常重要的。

视图对重构数据库提供了一定程度的逻辑独立性

  在数据库系统概论中已经介绍过数据的物理独立性与逻辑独立性的概念。数据的物理独立性是指 用户的应用程序不依赖于数据库的物理结构。数据的逻辑独立性是指当数据库重构造时, 如增加新的关系或对原有关系增加新的字段等,用户的应用程序不会受影响。层次数据库 和网状数据库一般能较好地支持数据的物理独立性,而对于逻辑独立性则不能完全地支持。在关系数据库中,数据库的重构往往是不可避免的。重构数据库最常见的是将一个基 本表"垂直"地分成多个基本表。例如:将学生关系

1
Student(Sno,Sname,Ssex,Sage,Sdept)

  分为SX(Sno, Sname, Sage)SY(Sno, Ssex, Sdept)两个关系。这时原表StudentSX表和SY表自然连接的结果。如果建立一个视图Student:

1
2
3
4
5
CREATE VIEW Student(Sno,Sname,Ssex,Sage,Sdept)
AS
SELECT SX.Sno,SX.Sname,SY.Ssex,SX.Sage,SY.Sdept
FROM SX.SY
WHERE SX.Sno=SY.Sno;

  这样尽管数据库的逻辑结构改变了(变为SX和SY两个表),但应用程序不必修改,因为新建立的视图定义为用户原来的关系,使用户的外模式保持不变,用户的应用程序通过视图仍然能够查找数据。
  当然,视图只能在一定程度上提供数据的逻辑独立性,比如由于对视图的更新是有条件的,因此应用程序中修改数据的语句可能仍会因基本表结构的改变而需要做相应修改。

视图能够对机密数据提供安全保护

  有了视图机制,就可以在设计数据库应用系统时对不同的用户定义不同的视图,使机密数据不出现在不应看到这些数据的用户视图上。这样视图机制就自动提供了对机密数据的安全保护功能。例如,Student表涉及全校15个院系的学生数据,可以在其上定义15个视图,每个视图只包含一个院系的学生数据,并只允许每个院系的主任查询和修改本院系的学生视图。

适当利用视图可以更清晰地表达查询

  例如,经常需要执行这样的查询“对每个同学找出他获得最高成绩的课程号”。可以先定义一个视图,求出每个同学获得的最高成绩:

1
2
3
4
5
CREATE VIEW VMGRADE
AS
SELECT Sno.MAX(Grade) Mgrade
FROM SC
GROUP BY Sno;

  然后用如下的查询语句完成查询:

1
2
3
SELECT SC.Sno,Cno
FROM SC, VMGRADE
WHERE SC.Sno=VMGRADE.Sno AND SC.Grade=VMGRADE.Mgrade;

不要温和地走进那个良夜。

迪兰·托马斯
本图书馆累计发布了20篇文章 共33.6万字
本图书馆访客数 访问量