第8章 高级查询
前面讲解的SQL查询都是比较简单、比较基础的SELECT语句查询。如果想从多个表中获得比较复杂的查询信息,就应该通过高级查询来实现。高级查询包括多表连接查询、内连接查询、外连接查询和组合查询等。通过使用高级查询,可以对一个表或者多个表进行操作。本章将详细讲解有关高级查询方面的相关知识。
8.1 连接查询
通过连接查询,可以根据各个表之间的逻辑关系从两个或多个表中检索数据。连接表示应如何使用一个表中的数据来选择另一个表中的行。
8.1.1 连接的概念
多表连接实际上就是实现如何使用一个表中的数据来选择另一个表中的行。而连接条件则主要通过以下方法定义两个表在查询中的关联方式:
- 指定每个表中要用于连接的列。典型的连接条件在一个表中指定外键,在另一个表中指定与其关联的键。
- 指定比较各列的值时要使用的逻辑运算符(=、<>等)。
说明:在SELECT连语句中实现的多表连接,并不是一个物理存在的实体,也就是说,它在数据库表中并不存在。它只是由数据库系统在需要的时候创建的,只在查询、检索数据期间有效。
下面给出一个典型的二表连接的例子。
【上机实战】
假设有两个表T1和T2,其包含的列和数据分别如表8.1和表8.2所示。
表8.1 T1数据库表
ColumnA ColumnB ColumnC
X1 Y1 Z1
X2 Y2 Z2
X3 Y3 Z3
表8.2 T2数据库表
ColumnA ColumnD ColumnE
X1 D1 E1
X2 D2 E2
X3 D3 E3
T1和T2表共有的列为ColumnA,如果通过ColumnA列的值连接T1和T2两个表,即连接条件为T1.ColumnA=T2.ColumnA,此时得到的连接结果如表8.3所示。
表8.3 连接T1和T2表
ColumnA ColumnB ColumnC ColumnD ColumnE
X1 Y1 Z1 D1 E1
X2 Y2 Z2 D2 E2
X3 Y3 Z3 D3 E3
上述连接过程的实现代码可表示如下:
SELECT *
FROM T1 JOIN T2
ON T1.ColumnA=T2.columnA
8.1.2 内连接
内部连接是使用比较运算符比较要连接列中的值的连接。内连接分为等值连接和不等值连接两种查询方式,下面分别讲解一下这两种方式的具体实现。
1.等值连接
等值连接是指在连接条件中使用等号(=)运算符比较被连接列的列值。其查询结果中列出了被连接表中所有的列,其中也包括重复的列值。
【语法说明】
等值连接的使用语法可简单表示如下。
SELECT 列
FROM 表1 [INNER] JION 表2
ON 表1.列=表2.列
在SQL-92 标准中,可以在FROM子句或WHERE子句中指定内部连接。此时其语法可简单表示如下。
SELECT 列
FROM 表1,表2
WHERE表1.列=表2.列
图8.1表示的是等值连接的示意图。
图8.1 等值连接示意图
下面通过一个示例来讲解内连接中的等值连接。
【上机实战】
通过内连接实现将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息。为了便于理解,现将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)中的数据信息显示出来。
显示图书信息表(T_B_INFOS)中的数据信息,其实现的代码如下所示。
SELECT * FROM T_B_INFOS
代码执行以后,其执行结果如图8.2所示。
图8.2 图书信息表(T_B_INFOS)中的数据信息
显示图书价格表(T_BOOKVALUE)中的数据信息,其实现的代码如下所示。
SELECT * FROM T_BOOKVALUE
代码执行以后,其执行结果如图8.3所示。
图8.3 图书价格表(T_BOOKVALUE)中的数据信息
使用内连接语句,将图书信息表(T_B_INFOS)与图书价格表(T_BOOKVALUE)连接起来的实现代码如下所示。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A INNER JOIN T_BOOKVALUE B
ON
A.TNO=B.B_ID
代码执行以后,其执行结果如图8.4所示。
图8.4 内连接查询的应用
从图中可以看出,使用内连接也可以将两个相关的数据表连接起来,查询所需要的数据信息。
使用ORDER BY子句还可以将通过内连接查询出的数据结果进行排序。
下面的代码实现的是,通过内连接实现将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息,并且将查询的结果按照图书编号(TNO)进行降序排序,其实现的代码如下所示。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A INNER JOIN T_BOOKVALUE B
ON
A.TNO=B.B_ID
ORDER BY A.TNO DESC
代码执行以后,其执行的结果如图8.5所示。
图8.5 将内连接的查询结果进行降序排序
2.不等值连接
内连接的另一种连接类型为不等值连接。不等值连接是指在连接条件中使用除等于(=)运算符以外的其他比较运算符比较被连接的列值。这些运算符包括>、>=、<、<=、!<等。
【语法说明】
不等值连接的使用语法可简单表示如下。
SELECT 列
FROM 表1 [INNER] JION 表2
ON 表1.列 <> 表2.列
在SQL-92 标准中,可以在FROM子句或WHERE子句中指定内部连接。此时其语法可简单表示如下。
SELECT 列
FROM 表1,表2
WHERE表1.列 <> 表2.列
下面通过一个示例来讲解内连接中的不等值连接。
【上机实战】
通过内连接实现将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息,并且列出查询结果所有不匹配的数据信息,其实现的代码如下所示。
图书信息表(T_B_INFOS)与图书价格表(T_BOOKVALUE)中的数据信息请分别参照图8.2与图8.3。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A INNER JOIN T_BOOKVALUE B
ON
A.TNO <> B.B_ID
代码执行以后,其执行结果如图8.6所示。
图8.6 内连接不等值查询的应用
8.1.3 使用表的别名
在前面讲解的示例中,在SELECT语句中,有关列名均使用了“Talbe_name.Column_name”的完整表达方式。当然,如果连接的两个表中没有相同的列名,则也可以省略表名。但是,如果两个表中含有相同的列名,则在使用该列时,必须指明其对应的表名。
然而,有时表名比较烦琐,使用起来很麻烦,为了程序的简洁明了,在SQL中,也可以通过AS关键字为表定义别名。
然而,与给列起别名不同,使用AS关键字为表起别名是在FROM子句中完成的。
【上机实战】
将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息,并且要求图书价格大于30元,其实现的代码如下所示。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A,T_BOOKVALUE B
WHERE
A.TNO=B.B_ID AND B.B_VALUE>30
代码执行以后,其执行结果如图8.7所示。
图8.7 使用表的别名
8.1.4 自连接
自连接是指使用表的别名实现表与其自身进行连接。
【上机实战】
通过自连接实现查询员工信息表(T_EMPLOY)中工资高于1000的员工姓名、部门、工资和奖金信息。为了便于读者进行分析比较,现将员工信息表(T_EMPLOY)中的数据信息显示出来。
SELECT * FROM T_EMPLOY
代码执行以后,执行结果如图8.8所示。
图8.8 员工信息表中的数据信息
使用自连接查询数据信息的实现代码如下所示。
SELECT A.E_NAME,A.E_BM,A.E_GZ,A.E_JJ
FROM T_EMPLOY A,T_EMPLOY B
WHERE A.E_ID=B.E_ID
AND B.E_GZ>1000
代码执行以后,执行结果如图8.9所示。
图8.9 自连接使用示例
从示例中可以看出,工资小于1000的员工信息没有显示。代码中,FROM子句中的A表和B表实际上都是T_EMPLOY表。只不过采用使用别名的方法使他们可以独立地进行使用。这样就可以在WHERE子句中,使用B设定查询条件,而在SELECT子句中,则使用A查询满足查询条件的数据信息。
8.1.5 自然连接
使用NATURAL JOIN关键字可以实现自然连接。自然连接是指将表中具有相同名称的列自动进行记录匹配。自然连接的典型示意图如图8.10所示。
图8.10 自然连接
自然连接不能人为地指定哪些列被匹配。只能够自动判断哪些列,然后在进行匹配。下面通过几个具体的示例来讲解自然连接的使用方法。
【上机实战】
采用自然连接将T_B_INFOS数据表和T_BOOKINFO数据表连接起来,并且显示其中的数据信息。为了便于读者进行分析比较,现将T_B_INFOS数据表和T_BOOKINFO数据表中的数据信息显示出来。
显示T_B_INFOS数据表中的数据信息。
SELECT * FROM T_B_INFOS
显示T_BOOKINFO数据表中的数据信息。
SELECT * FROM T_BOOKINFO
上述代码运行后,其结果分别如图8.11与8.12所示。
图8.11 T_B_INFOS数据表中的数据信息
图8.12 T_BOOKINFO数据表中的数据信息
通过自然连接查询数据信息的实现代码如下所示。
SELECT TNO,TNAME,B_ID,B_NAME
FROM T_B_INFOS
NATURAL JOIN T_BOOKINFO
代码执行以后,执行结果如图8.13所示。
图8.13 自然连接应用示例
在SQL Server中,不支持NATURAL JOIN连接符。
8.1.6 左外连接–LEFT OUTER JOIN
左外连接(LEFT OUTER JOIN)是指将左表中的所有数据分别与右表中的每条数据进行连接组合,返回的结果除内连接的数据外,还包括左表中不符合条件的数据,并在右表的相应列中填加NULL值。图8.14表示的是左外连接的示意图。
图8.14 左外连接的示意图
【上机实战】
通过左外连接将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息。
为了便于比较,现将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)中的数据信息显示出来。
显示图书信息表(T_B_INFOS)中的数据信息,其实现的代码如下所示。
SELECT * FROM T_B_INFOS
代码执行以后,其执行结果如图8.15所示。
图8.15 图书信息表(T_B_INFOS)中的数据信息
显示图书价格表(T_BOOKVALUE)中的数据信息,其实现的代码如下所示。
SELECT * FROM T_BOOKVALUE
代码执行以后,其执行结果如图8.16所示。
图8.16 图书价格表(T_BOOKVALUE)中的数据信息
使用左外连接查询数据信息的实现代码如下所示。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A LEFT OUTER JOIN T_BOOKVALUE B
ON
A.TNO=B.B_ID
ORDER BY A.TNO ASC
代码执行以后,其执行结果如图8.17所示。
图8.17 左外连接应用示例
从图中可以看出,数据表中图书编号为1007、1008、1005和1006的记录中的B_VALUE和B_NUMS字段中的值为NULL。这是由于在执行左外连接之后,图书价格表(T_BOOKVALUE)没有图书编号为1007、1008、1005和1006的记录,因而在图书信息表(T_B_INFOS)中找不到与其相匹配的数据记录,所以这几个字段中的数据为NULL。
使用ORDER BY子句还可以将通过左外连接查询出的数据结果进行排序,我们再看下面的示例。
通过左外连接将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息,并且将查询的结果按照图书编号(TNO)进行降序排序,其实现的代码如下所示。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A LEFT OUTER JOIN T_BOOKVALUE B
ON
A.TNO=B.B_ID
ORDER BY A.TNO ASC
代码执行以后,其执行结果如图8.18所示。
图8.18 将左外连接查询结果进行排序
8.1.7 右外连接–RIGHT OUTER JOIN
右外连接(RIGHT OUTER JOIN)是指将右表中的所有数据分别与左表中的每条数据进行连接组合,返回的结果除内连接的数据外,还包括右表中不符合条件的数据,并在左表的相应列中填加NULL。图8.19表示的是右外连接的示意图。
图8.19 右外连接的示意图
【上机实战】
通过右外连接将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息,其实现的代码如下所示。
图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)中的数据信息请分别参照图8.15与图8.16。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A RIGHT OUTER JOIN T_BOOKVALUE B
ON
A.TNO=B.B_ID
ORDER BY A.TNO ASC
代码执行以后,其执行结果如图8.20所示。
图8.20 右外连接应用示例
从图中可以看出,数据表中第4行记录中的TNO、TNAME和TPUBLISH字段中的值为NULL。这是由于在执行右外连接之后,图书信息表(T_B_INFOS)中没有图书编号为1004的记录,因而在图书价格表(T_BOOKVALUE)中找不到与其相匹配的数据记录,所以这几个字段中的数据为NULL。
说明:在SQL Server数据库中,使用“=*”可以代替RIGHT OUTER JOIN关键字实现右外连接。
8.1.8 全外连接–FULL JOIN
全外连接是指将左表中的所有数据分别与右表中的每条数据都进行连接组合。返回的结果集除了内连接的数据外,还有两个表中不符合条件的数据,并在左或右表的相应列中填加NULL。图8.21表示的是全外连接的示意图。
图8.21 全外连接的示意图
【上机实战】
通过全外连接将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,同时查询图书编号(TNO)、图书名称(TNAME)、出版社(TPUBLISH)、图书价格(B_VALUE)和图书本数(B_NUMS)信息,其实现的代码如下所示。
图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)中的数据信息请分别参照图8.15与图8.16。
SELECT A.TNO,A.TNAME,A.TPUBLISH,B.B_VALUE,B.B_NUMS
FROM T_B_INFOS A FULL JOIN T_BOOKVALUE B
ON
A.TNO=B.B_ID
ORDER BY A.TNO ASC
代码执行以后,其执行结果如图8.22所示。
图8.22 全外连接应用示例
从图中可以看出,在执行全外连接之后,图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)中所有相匹配的数据信息全部都被显示出来。
8.1.9 在外连接中使用聚合函数
在外连接中也可以使用聚合函数来进行数值运算。下面通过示例来进行说明。
【上机实战】
通过左外连接将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,并且从图书信息表(T_B_INFOS)中查询所有图书的图书出版社信息,从图书价格表(T_BOOKVALUE)中查询该学生的最高科目的成绩、最低科目的成绩和所有科目的平均成绩信息,其实现的代码如下所示。
SELECT TPUBLISH,
Max(B_VALUE) 最高价格,
Min(B_VALUE) 最低价格,
Avg(B_VALUE) 平均价格
FROM T_B_INFOS R
LEFT OUTER JOIN T_BOOKVALUE S
ON R.TNO =S.B_ID
GROUP BY R.TPUBLISH
代码执行以后,其执行的结果如图8.23所示。
图8.23 在外连接中使用聚合函数
下面再看一个在全外连接中使用聚合函数的示例。
通过全外连接将图书信息表(T_B_INFOS)和图书价格表(T_BOOKVALUE)连接起来,并且从图书信息表(T_B_INFOS)中查询所有图书的图书出版社信息,从图书价格表(T_BOOKVALUE)中查询该学生的最高科目的成绩、最低科目的成绩和所有科目的平均成绩信息,其实现的代码如下所示。
SELECT TPUBLISH,
Max(B_VALUE) 最高价格,
Min(B_VALUE) 最低价格,
Avg(B_VALUE) 平均价格
FROM T_B_INFOS R
FULL JOIN T_BOOKVALUE S
ON R.TNO =S.B_ID
GROUP BY R.TPUBLISH
代码执行以后,其执行的结果如图8.24所示。
图8.24 在外连接中使用聚合函数
在使用UNION运算符得到的结果集中,列名与UNION运算中第一个SELECT语句的结果集中的列名相同,其他的SELECT语句的结果集列名将被忽略。使用UNION运算符还可以连接常量和变量。如下面的代码所示。
SELECT 100 COL1,200 COL2 FROM DUAL
UNION
SELECT 300,500 FROM DUAL
UNION
SELECT 100*3,200*3 FROM DUAL
UNION
SELECT 900, 400 FROM DUAL
代码执行以后,其执行结果如图8.25所示。
图8.25 使用UNION运算符连接常量和变量结果集
8.2 组合查询
如果有多个不同的查询结果数据集,但又希望将它们按照一定的关系连接在一起,组成一组数据,这就可以使用组合查询来实现。
8.2.1 集合运算基础
集合的3个最基本的运算是并、交和差。对于任意集合R和S(当然,这里的R和S可以是表R和表S),这些运算定义如下。
- R并S,R或S或两者中元素的集合。一个元素在并集中只出现一次,即使它在R和S中都存在。
- R交S,R和S中都存在的元素的集合。
- R差S,在R中而不在S中的元素的集合。注意R差S不同于S差R,后者是在S中而不在R中的元素的集合。
关于交、并、差运算的示意图如图8.26所示。
图8.26 集合运算示意图
在Transact-SQL中,可以使用UNION运算符实现集合并的运算,EXCEPT运算符实现集合差的运算,而INTERSECT运算符实现集合交的运算。
8.2.2 使用UNION组合查询结果
使用UNION运算符可以将两个或更多查询的结果合并为单个结果集,该结果集包含联合查询中的所有查询的全部行。UNION运算不同于使用连接合并两个表中的列的运算。
【语法说明】
使用UNION组合查询的语法结构如下所示。
{ <query specification>|( <query expression> ) }
UNION [ ALL ]
<query specification | ( <query expression> )
[ UNION [ ALL ] <query specification> | ( <query expression> ) [ ...n ] ]
其中:
- |( ):查询规范或查询表达式,用以返回与另一个查询规范或查询表达式所返回的数据合并的数据。作为UNION运算一部分的列定义可以不相同,但它们必须通过隐式转换实现兼容。
- UNION:指定合并多个结果集并将其作为单个结果集返回。
- ALL:将全部行并入结果中,其中包括重复行。如果未指定该参数,则删除重复行。
使用UNION合并两个查询结果集,必须满足下面的基本准则:
- 在用UNION运算符组合的语句中,所有选择列表中的表达式(如列名称、算术表达式、聚合函数等)数目必须相同。
- 用UNION组合的结果集中的对应列,或者各个查询中所使用的列都必须具有相同的数据类型,并且可以在两种数据类型之间进行隐式数据转换,或者可以提供显式转换。例如,datetime数据类型的列和binary数据类型的列之间的UNION运算符将不执行运算,直到进行了显式转换。但是,money数据类型的列和int数据类型的列之间的UNION运算符将执行运算,因为它们可以进行隐式转换。
- 用UNION运算符组合的各语句中对应结果集列的顺序必须相同,因为UNION运算符按照各个查询中给定的顺序一对一地比较各列。
注意:使用UNION运算符对集合进行并运算,得到的结果集的记录自动按其第一列的值的升序的顺序进行排列。
【上机实战】
查询员工信息表(T_EMPLOY)中“信息表”所有员工信息以及所有工资大于1200的员工信息,为了便于理解,现将员工信息表(T_EMPLOY)中的数据信息显示出来。
SELECT * FROM T_EMPLOY
代码执行以后,其执行结果如图8.27所示。
图8.27 员工信息表(T_EMPLOY)中的数据信息
使用UNION语句查询数据信息的实现代码如下所示。
SELECT * FROM T_EMPLOY
WHERE E_BM='信息部'
UNION
SELECT * FROM T_EMPLOY
WHERE E_GZ>1200
代码执行以后,其执行结果如图8.28所示。
图8.28 UNION语句的应用实例
注意:UNION运算符执行集合并运算,自动从结果表中消除重复的行。如果不消除重复的行,则可以使用UNION ALL运算符。
下面再通过实例来讲解一下UNION ALL运算符的使用。
使用UNION ALL运算符查询员工信息表(T_EMPLOY)中“信息表”所有员工信息,以及所有工资大于1200的员工信息,其实现的代码如下所示。员工信息表(T_EMPLOY)中的数据信息请参照图8.29。
SELECT * FROM T_EMPLOY
WHERE E_BM='信息部'
UNION ALL
SELECT * FROM T_EMPLOY
WHERE E_GZ>1200
代码执行以后,其执行结果如图8.29所示。
图8.29 UNION ALL运算符的应用实例
8.2.3 使用UNION组合多个数据表
采用UNION运算符也可以组合两个以上的表,组合过程也是两两进行的。如果要UNION运算符组合A、B、C三个表。
【语法说明】
SELECT * FROM A
UNION
SELECT * FROM B
UNION
SELECT * FROM C
DBMS将根据图8.30所示的工作流程来执行UNION运算。
图8.30 UNION运算的工作流程
Transact-SQL语句中可以出现任意数目的UNION运算符。在默认情况下,SQL Server 2005从左向右对包含UNION运算符的语句求值。但是,可使用圆括号指定求值顺序。
【上机实战】
查询T_B_INFOS数据表中的图书编号(TNO)、图书名称(TNAME)和出版社(TPUBLISH)数据信息,同时查询T_BOOKINFO数据表中“远方出版社”和图书名称类似于“SQL”字符的数据信息,并且使用UNION语句将查询的结果集连接起来,其实现的代码如下所示。
SELECT TNO,TNAME,TPUBLISH
FROM T_B_INFOS
UNION
SELECT B_ID,B_NAME,B_PUBLISH
FROM T_BOOKINFO WHERE B_PUBLISH='远方出版社'
UNION
SELECT B_ID,B_NAME,B_PUBLISH
FROM T_BOOKINFO WHERE B_NAME LIKE '%SQL%'
代码执行以后,其执行结果如图8.31所示。
图8.31 使用UNION语句连接多个数据表中的数据信息
8.2.4 UNION与UNION ALL组合应用
如果所有的表都使用UNION运算符组合,或者都使用UNION ALL运算符组合,无论采用什么样的组合顺序,结果都是一样的。但是,如果UNION运算符与UNION ALL运算符混合使用,则不同的组合顺序,得到的结果可能不一样。
如“(A UNION ALL B)UNION C”,执行过程及结果如图8.32所示。
图8.32 UNION运算过程
而“A UNION ALL(B UNION C)”,执行过程及结果如图8.33所示。
图8.33 UNION运算过程
可见,不同的执行顺序得到了不同的结果,这在实际应用中要特别注意。
【上机实战】
查询T_B_INFOS数据表中的图书编号(TNO)、图书名称(TNAME)和出版社(TPUBLISH)数据信息,同时查询T_BOOKINFO数据表中“远方出版社”和图书名称类似于“SQL”字符的数据信息,并且使用UNION和UNION ALL运算符将查询的结果集连接起来,其实现的代码如下所示。
SELECT TNO,TNAME,TPUBLISH
FROM T_B_INFOS
UNION
SELECT B_ID,B_NAME,B_PUBLISH
FROM T_BOOKINFO WHERE B_PUBLISH='远方出版社'
UNION ALL
SELECT B_ID,B_NAME,B_PUBLISH
FROM T_BOOKINFO WHERE B_NAME LIKE '%SQL%'
代码执行以后,其执行结果如图8.34所示。
图8.34 UNION和UNION ALL运算符的组合应用
如果调换UNION和UNION ALL运算符的顺序,其查询的结果也会发生改变,如下面的代码所示。
SELECT TNO,TNAME,TPUBLISH
FROM T_B_INFOS
UNION ALL
SELECT B_ID,B_NAME,B_PUBLISH
FROM T_BOOKINFO WHERE B_PUBLISH='远方出版社'
UNION
SELECT B_ID,B_NAME,B_PUBLISH
FROM T_BOOKINFO WHERE B_NAME LIKE '%SQL%'
代码执行以后,其执行结果如图8.35所示。
图8.35 UNION和UNION ALL运算符的组合应用
8.2.5 集合差/集合交运算——EXCEPT/INTERSECT
使用EXCEPT运算符可以实现集合差操作,即从左查询中返回右查询没有找到的所有非重复值。而使用INTERSECT运算符可实现集合交操作,即返回INTERSECT操作符左右两边的两个查询都返回的所有非重复值。
【语法说明】
EXCEPT运算符的使用方法如下所示。
{ <query_specification> | ( <query_expression> ) }
{ EXCEPT | INTERSECT }
{ <query_specification> | ( <query_expression> ) }
使用EXCEPT或INTERSECT比较的结果集必须具有相同的结构,即它们的列数必须相同,并且相应的结果集列的数据类型必须兼容。
使用EXCEPT和INTERSECT运算符进行集合运算时,需注意以下几点:
- 如果EXCEPT或INTERSECT操作符左边和右边的查询返回的可比较列的数据类型是具有不同排序规则的字符数据类型,则根据排序规则优先级的规则执行所需的比较。
- 通过比较行来确定非重复值时,两个NULL值被视为相等。
- EXCEPT或INTERSECT返回的结果集的列名与操作数左侧的查询返回的列名相同。
- EXCEPT或INTERSECT返回的结果集中的任何列的为空性(是否可以为空)与操作数左侧的查询返回的对应列的为空性相同。
如果EXCEPT或INTERSECT与表达式中的其他运算符一起使用,则按以下优先顺序对其进行运算:
(1)括号中的表达式。
(2)INTERSECT操作符。
(3)基于在表达式中的位置从左到右求值的EXCEPT和UNION运算。
如果EXCEPT或INTERSECT用于比较两个以上的查询集,则数据类型转换是通过一次比较两个查询来确定的,并遵循前面提到的表达式求值规则。
【上机实战】
为了演示EXCEPT/INTERSECT运算符的使用方法,这里首先创建3个数据表:tableA、tableB和tableC,创建代码如下所示。
CREATE TABLE tableA(a int NULL); --创建tableA表
CREATE TABLE tableB(b int NULL); --创建tableB表
CREATE TABLE tableC(c int NULL) --创建tableC表
使用Transact-SQL的INSERT INTO语句,向talbeA表、tableB表和tableC表中添加数据,代码如下所示。
INSERT INTO tableA VALUES(NULL); --向tableA表添加数据
INSERT INTO tableA VALUES(NULL); --向tableA表添加数据
INSERT INTO tableA VALUES(NULL); --向tableA表添加数据
INSERT INTO tableA VALUES(1); --向tableA表添加数据
INSERT INTO tableA VALUES(2); --向tableA表添加数据
INSERT INTO tableA VALUES(2); --向tableA表添加数据
INSERT INTO tableA VALUES(2); --向tableA表添加数据
INSERT INTO tableA VALUES(3); --向tableA表添加数据
INSERT INTO tableA VALUES(4); --向tableA表添加数据
INSERT INTO tableA VALUES(4); --向tableA表添加数据
INSERT INTO tableB VALUES(NULL); --向tableB表添加数据
INSERT INTO tableB VALUES(1); --向tableB表添加数据
INSERT INTO tableB VALUES(3); --向tableB表添加数据
INSERT INTO tableB VALUES(4); --向tableB表添加数据
INSERT INTO tableB VALUES(4); --向tableB表添加数据
INSERT INTO tableC VALUES(2); --向tableC表添加数据
INSERT INTO tableC VALUES(2); --向tableC表添加数据
INSERT INTO tableC VALUES(2); --向tableC表添加数据
INSERT INTO tableC VALUES(4); --向tableC表添加数据
INSERT INTO tableC VALUES(4); --向tableC表添加数据
此时,tableA表、tableB表和tableC表的结构及包含的数据如图8.36所示。
图8.36 tableA表、tableB表和tableC表的结构及包含的数据
使用EXCEPT运算符可以实现集合差操作,从左查询中返回右查询没有找到的所有非重复值。
下面的代码将对tableA表和tableB表使用EXCEPT运算符实现集合差运算。
SELECT * FROM TableA
EXCEPT
SELECT * FROM TableB
运行代码,其执行结果如图8.37所示。
图8.37 EXCEPT运算符的应用示例
从结果可以发现,结果集中并没有出现NULL值。这是因为tableA表的列中含有NULL值,而用于集合差操作的tableB表列中同样含有NULL值,在进行EXCEPT运算时,NULL值被视为相等,因此结果集中就不含有NULL值了。
我们再看下面的示例。
使用INTERSECT运算符可实现集合交操作,返回INTERSECT操作符左右两边的两个查询都返回的所有非重复值。
下面的代码将对tableA表和tableB表使用INTERSECT运算符实现集合交运算,其实现的代码如下所示。
SELECT * FROM TableA
INTERSECT
SELECT * FROM TableB
运行代码,其执行结果如图8.38所示。
图8.38 INTERSECT运算符的应用示例
使用EXCEPT和INTERSECT运算符也可以组合两个以上的表,但如果INTERSECT运算符和EXCEPT运算符同时使用,则INTERSECT运算符优先于EXCEPT运算符。
使用EXCEPT和INTERSECT运算符也可以组合两个以上的表,但如果INTERSECT运算符和EXCEPT运算符同时使用,则INTERSECT运算符优先于EXCEPT运算符,请看下面的代码。
对tableA表、tableB表和tableC表使用EXCEPT和INTERSECT运算符实现集合的相关运算,通过结果分析说明EXCEPT和INTERSECT运算符的优先级。实例代码如下:
SELECT * FROM TableA
EXCEPT
SELECT * FROM TableB
INTERSECT
SELECT * FROM TableC
运行代码,结果如图8.39所示。
图8.39 EXCEPT和INTERSECT运算符的组合使用
很显然,首先执行了tableB表和tableC表的交运算,而后再与tableA表进行差运算。如果要先执行tableA表与tableB表的差运算,而后再与TalbeC表进行交运算,则必须使用括号改变运算的先后顺序,其实现的代码如下所示。
(SELECT * FROM TableA
EXCEPT
SELECT * FROM TableB)
INTERSECT
SELECT * FROM TableC
运行代码,结果如图8.40所示。
图8.40 EXCEPT和INTERSECT运算符的组合使用