4.6 深入SQL查询语言

上一节介绍了各种简单的查询子句,这些查询都是对一个表进行操作。在本节中会进一步讲解比较复杂的SQL查询语句,主要涉及集合查询、多表查询和嵌套查询。

4.6.1 集合查询

SQL通常会包含一些集合运算,一般都支持并(UNION)运算,有些SQL版本还有交(INTERSECTION)或差(MINUS)集合运算。SELECT语句查询的结果是元组的集合,多个SELECT语句的结果可以进行集合操作。但是只有查询结果互相兼容,才能进行集合运算,即参与运算的查询结果必须具有相同的列数,并且对应列的属性域相同。

例如查询选修课程C001的以及年龄小于22的学生的学号,SQL语句可以如下表达:

    SELECT SNO FROM STUDENTS WHERE SAGE<22
    UNION SELECT SNO FROM SC WHERE CNO='C001';

★ 说明 ★

使用UNION运算将多个查询结果合并起来时,系统会自动去掉重复元组。

标准的SQL中没有直接提供集合交运算和差运算,如果读者使用的DBMS支持这些运算,可以参考DBMS厂商提供的参考手册。

4.6.2 连接查询

连接查询也叫多表查询,通过连接运算符可以实现多个表查询。连接是关系数据库模型的主要特点,也是它区别于其他类型数据库管理系统的一个标志。

在关系数据库管理系统中,表建立时各数据之间的关系不必确定,常把一个实体的所有信息存放在一个表中。当检索数据时,通过连接操作查询出存放在多个表中的不同实体的信息。连接操作给用户带来很大的灵活性,他们可以在任何时候增加新的数据类型。为不同实体创建新的表,尔后通过连接进行查询。

连接查询包括等值和非等值连接查询、自身连接查询、外连接查询和复合条件连接查询。

1.等值和非等值连接查询

连接查询中用来连接两个表的条件称为连接条件或连接谓词,其格式为:

[<表名1>.]<列名1><比较运算符>[<表名2>.]<列名2>其中比较运算符有:“=、>、<、>=、<=、!=”。比较运算符为“=”时,称为等值连接,其他的称为非等值连接。连接条件中的列名所指定的字段值必须是可比的,但不一定要求是相同的。

例如要查询学生以及学生选课的信息,需要查询学生表(STUDENTS)和学生选课表(SC) 2个表。这两个表是通过SNO字段相关的。使用等值连接的SQL语句可以如下表达:

    SELECT STUDENTS.*,SC.* FROM STUDENTS,SC WHERE STUDENTS.SNO=SC.SNO;

2.自身连接查询

连接操作不仅可以用于两个表之间,也可以是一个表与其自己进行连接,称为自身连接查询。当一个表所代表的实体之间有关系时,往往可以用到自身连接查询。例如,前面代表课程的表COURSE,如果课程之间有“先修”关系,则可添加一个字段CPRE,数据模式为COURCE(CNO,CNAME,CCRDIT,CPRE),该字段的值表示某课程先修课程的编号CNO。假设有一COURCE表实例,如表4-9所示,CPRE字段值和CNO字段是可比的。

表4-9 COURCE表

例如要查询各课程的间接先修课程,实现时为了清楚,可以为COURCE表起两个别名,假设为CR1和CR2。这样一来实现该查询使用的自身连接查询语句如下:

    SELECT CR1.CNO,CR2.CPRE FROM COURCE CR1,COURCE CR2 WHERE CR1.CPRE=CR2.CNO;

实现结果如表4-10所示。

表4-10 查询结果

3.外连接查询

在普通的连接操作中,只有满足连接条件的记录才能作为结果输出,在前面讲解等值和非等值连接时的例子中,如果有学生没有选课则选课表(SC)中就没有对应的该学生学号,那么最后得到的结果中也没有该学生的其他信息(即STUDENTS表中的信息)。但实际使用时,也许需要以显示所有学生信息为要求,如果没有选课,可以在选课信息中设置为空值。这里就用到了外连接查询。上面的需求可以用如下SQL实现:

    SELECT STUDENTS.SNO,SNAME,SSEX,SDEPT,CNO,GRADE
    FROM STUDENTS,SC WHERE STUDENTS.SNO=SC.SNO(*);

外连接的表示方法为,在连接谓词的某一边加上符号*。外连接类似于给添加*的表添加了一个“任意匹配”行,该行记录能够和另一边的表中所有没有符合连接条件的记录匹配。这样的结果是,即使选课表SC中没有某学生的选课记录,也可以获得该学生的STUDENTS表的记录,但是其SC表中的信息字段都为空。

4.复合连接查询

上面介绍的连接查询都是只有一个WHERE子句查询条件的,实际上WHERE子句中可以有多个连接条件,这样的查询称为复合条件查询。多个条件使用“AND”关键词连接即可,在这里就不再举例说明。

4.6.3 嵌套查询

在SELECT查询语句里可以嵌入SELECT查询语句,称为嵌套查询。有些书上将内嵌的SELECT语句称为子查询,子查询形成的结果又成为父查询的条件。SQL语言允许多层嵌套查询,即一个子查询中还可以有其他子查询。子查询操作的数据表可以是父查询不操作的数据表。子查询中不能有ORDER BY子句,ORDER BY子句只能对最终查询结果排序。

嵌套查询的求解方法是由里向外处理。即每个子查询在上一级查询处理之前求解,子查询的结果用于建立其父查询的查找条件。下面通过例子来说明。

例如要查询选择课程C002的所有学生的名字和系别,SQL语句如下:

    SELECT SNAME,SDEPT FROM STUDENTS WHERE SNO IN
    SELECT SNO FROM SC WHERE CNO='C002';

本例中,下层SELECT SNO FROM SC WHERE CNO=’C002’查询子句是嵌套在上层查询SELECT SNAME,SDEPT FROM STUDENTS WHERE SNO IN的查询条件中的。下层查询子句称为子查询,上层查询称为父查询。

嵌套查询可以用多个简单的查询构成复杂的查询。嵌套查询根据查询条件的不同具体分为以下四种。

1.带有IN的子查询

子查询结果一般都是一个集合,所以上层查询条件中使用IN的情况最多。例如要查询选修了课程名为C++的学生的姓名和系别,SQL语句如下:

    SELECT SNAME,SDEPT FROM STUDENTS WHERE SNO IN
        (SELECT SNO FROM SC WHERE CNO IN
            (SELECT CNO FROM COURSE WHERE CNAME='C++'));

本例中使用了三层嵌套,最底层的查询子句用来查询课程C++的课程序号,第二层的查询子句根据课程序号查询选修该课程的学生的学号,最上层的查询根据学号完成查询学生姓名和系别的任务。

从例子中可以看出,查询设计多个表时,用嵌套查询逐步求解,层次清晰,容易理解,具有结构化程序设计的优点。

2.带有比较运算符的子查询

带有比较运算符的子查询是指父查询与子查询之间用比较运算符进行连接。当用户能确切知道子查询返回的是单值时,可以用>,<,=,>=,<=,!=或<>等比较运算符。

例如要查询和王雪同学在同一个系的学生的姓名和年龄,由子查询获得王雪的系别,该子查询结果必定为一个值,所以在这里可以用“=”,SQL语句如下:

    SELECT SNAME,SAGE FROM STUDENTS WHERE SDEPT=
        (SELECT SDEPT FROM STUDENTS WHERE SNAME='王雪');

3.带有ANY或ALL的子查询

使用ANY或ALL谓词时则必须同时使用比较运算符。具体的语义如下:

● >ANY:大于子查询结果中的某个值

● >ALL:大于子查询结果中的所有值

● <ANY:小于子查询结果中的某个值

● <ALL:小于子查询结果中的所有值

● >=ANY:大于等于子查询结果中的某个值

● >=ALL:大干等于子查询结果中的所有值

● <=ANY:小于等于子查询结果中的某个值

● <=ALL:小于等于子查询结果中的所有值

● =ANY:等于子查询结果中的某个值

● =ALL:等于子查询结果中的所有值(没有实际意义)

● !=(或<>=ANY:不等于子查询结果中的某个值

● !=(或<>)ALL:不等于子查询结果中任何一个值

例如要查询数学系中比英语系中某个学生年龄小的学生的名字和年龄,SQL语句如下:

    SELECT SNAME,SAGE FROM STUDENTS
    WHERE SAGE<ANY (SELECT SAGE FROM STUDENTS WHERE SDEPT='English')
    AND SDEPT='Maths';

★ 注意 ★

最后一行的AND是父查询的条件。

4.带有EXISTS的子查询

EXISTS代表存在量词∃。带有EXISTS的子查询不返回任何数据,只产生逻辑真值“true”或逻辑假值“false”。

在讲解这种子查询之前,先介绍两个概念:不相关子查询和相关子查询。在之前的子查询例子中可以看出,各子查询都只执行一次查询操作,其结果集合用于父查询。子查询的查询条件不依赖于父查询,这类子查询称为不相关子查询。而在带有EXISTS的子查询中,查询条件依赖于父查询,这类子查询就称为相关子查询。

例如,查询选修了课程号为C001的课程的学生姓名和系别,SQL语句如下:

    SELECT SNAME,SDEPT FROM Student WHERE EXISTS
    (SELECT * FROM SC WHERE SNO=STUDENTS.SNO AND CNO='C001');

使用存在量词EXISTS后,若内层查询结果非空,则外层的WHERE子句返回真值,否则返回假值。

由EXISTS引出的子查询,其目标列表达式通常都用“*”,因为带EXISTS的子查询只返回真值或假值,给出列名无实际意义。

在本例中可以看到子查询是依赖于父查询中的某个属性值(STUDENTS.SNO),求解相关子查询不能像求解不相关子查询那样,一次将子查询求解出来,然后求解父查询。内层查询由于与外层查询有关,因此必须反复求值。相关子查询的一般处理过程是:

首先取外层查询中(STUDENTS)表的第1个元组,根据它与内层查询相关的属性值(SNO值)处理内层查询,若WHERE子句返回值为真,则取此元组放入结果表;然后再取(STUDENTS)表的下一个元组;重复这一过程,直至外层(STUDENTS)表全部检查完为止。

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