8.4 练习与实训
图8.14 查询中包含计算生成的列
(5)SELECT子句中显示选项TOP|ALL|DISTINCT的使用
例8.5 查询选课学生的学号。
USE jxgl
SELECT SNO
FROM SC
GO
查询执行结果如图8.15(a)所示。显而易见,结果集中出现了若干相同的行。原因是同一个学生可能选修了几门课程。要避免显示重复的行,可在SELECT关键字后面加DISTINCT选项,如图8.15(b)所示。
SELECT子句中可以使用选项<TOP n>指定显示查询结果中前面n行记录。
例8.6 查询学生表,仅显示前面5行数据。
完成此要求的查询语句为
USE jxgl
SELECT TOP 5倡

图8.15 例8.5执行结果
FROM student
GO
语句执行结果如图8.16所示。

图8.16 使用TOP n选项指定仅显示结果集中位于前面的n行数据
SELECT子句中的ALL选项用于要求显示查询结果集中的所有记录行。实际上,如果没有TOP,DISTINCT选项,则默认就是显示所有查询出来的记录。请读者自行执行带有ALL和不带有ALL选项的查询语句进行对比。
(6)WHERE子句的作用
WHERE子句的作用是指定查询条件,其一般格式如下:
WHERE<条件表达式>
说明:
①条件表达式包括关系表达式、逻辑表达式和特殊表达式等类型。
②WHERE子句必须紧跟在FROM子句后。
SQL Server将仅返回符合指定条件的那些记录数据。符合条件的含义是<条件表达式>为逻辑真。下面举例介绍WHERE子句中各类表达式的构成和应用。
1)关系表达式在WHERE中应用
关系表达式使用6个关系运算符构成:<(小于)、<=(小于等于)、=(等于)、>=(大于等于)、>(大于)、!=或<>(不等于)。关系表达式的运算结果为逻辑真或逻辑假。
例8.7 查询所有男生的全部信息。
查询语句如下:
USE jxgl
SELECT 倡FROM student
WHERE sex=′男′
GO
执行结果如图8.17所示。

图8.17 查询所有男生的全部数据
例8.8 查询1995年以后出生的学生。
查询语句为
use jxgl
select倡from student
where birthday>′1995-01-01′
go
执行结果如图8.18所示。

图8.18 以指定日期作为查询条件
注意:
①对于字符型常量、日期常量须用单引号括起。
②关系运算中,字符的大小是以其ASCII码值的大小进行比较的。汉字是以其拼音字母的ASCII值进行比较。比较是从左至右逐个字符相比。例如,′b′较′a′大,′abc′小于′abd′,′黄′(拼音为huɑng)大于′华′(拼音为huɑ)。
2)用逻辑表达式构成查询条件
逻辑表达式由逻辑运算符连接关系表达式构成。逻辑运算符只有NOT(非)、AND(与)以及OR(或)3种,其中,NOT运算优先级最高,OR运算优先级最低。
例8.9 查询1995年后出生的男生的信息。
use jxgl
select倡from student
where birthday>′1995-01-01′and sex=′男′
go
执行结果如图8.19所示。

图8.19 用逻辑表达式构成的查询条件
3)使用特殊运算符构成条件表达式
SQL查询语句中的条件表达式还可以使用一些特殊的运算符构成,常用的几个特殊运算符及其含义见表8.1。
表8.1 几个常用的特殊运算符

例8.10 查询1995年内出生的学生的基本信息。
use jxgl
select-from student
where birthday between′1995-01-01′and′1995-12-31′
go
执行结果如图8.20所示。
想一想:一是本例的条件表达式如果使用关系运算符和逻辑运算符该如何表达?二是与NOT BETWEEN爥AND等价的逻辑运算是什么?试举例上机检验。
例8.11 查询选修表中尚无成绩的情况。
查询语句如下:
use jxgl
select-from sc
where score is null
go
查询执行结果如图8.21所示。

图8.20 使用BETWEEN爥AND构成查询条件表达式

图8.21 使用ISNULL构成查询条件表达式
说明:
表中尚无数据即为NULL。NULL的含义是“空”“没有数据”。必须强调的是,NULL不是“空字符”(即空格),也不是“0”。
本例要求查询的条件可以表达为:dept=′财经管理′OR dept=′机电技术′。
不难看出,当需要列举的关系表达式很多时,整个条件表达式就会非常繁长。而选用集合运算IN则可以化繁为简。
查询语句如下:
use jxgl
select倡from studentwhere dept in(′财经管理′,′机电技术′)
go
执行结果如图8.22所示。

图8.22 使用集合运算符IN构成条件表达式
4)模糊查询的条件表达式
前述各种条件查询均属于条件判别的“值”是确定的情况,如“年龄>20”“姓名=‘张三’”,等等。实际中还会遇到执行并不能确定条件取值的查询任务,如“查询姓张的老师”“查询姓名中包含‘建国’二字的学生”“查询学号不包含‘3’的学生”,等等。这类任务一般称为“模糊查询”。
SQL中提供了几个进行模糊查询的关键字和运算符号,见表8.2。
表8.2 几个常用的模糊查询关键字和运算符号

例8.13 查询所有张姓同学的记录。
SQL查询语句如下:
use jxgl
go
select倡from studentwhere sn like′张%′
go
执行结果如图8.23所示。

图8.23 使用%通配符实现模糊查询
例8.14 查询姓名中含有“国”“华”字样的学生。
查询语句为:
use jxgl
go
select倡from student
where sn LIKE′%[国华]%′
go
注意:这个表达式中[国华]只是“国”“华”中任意一个字符,并非“国华”二字。[]前面的通配符%意为“任何姓氏”,[]后面的通配符%表示“国”或“华”之后可有任何字符、也可没有字符。此查询执行结果如图8.24所示。

图8.24 使用特殊运算符[]完成模糊查询
想一想:假如学生中有姓“国”的同学,上面的查询能否查询到他们?请上机实测,检验你的分析、判断是否正确。
例8.15 查询姓名中含有“国”或“华”字样且其学号末位非“2”非“4”的学生。
查询语句为
use jxgl
go
select倡from student
where sn LIKE′%[国,华]%′ AND sno LIKE′%[^24]′
go
执行结果如图8.25所示。

图8.25 使用特殊运算符[]和[^]完成模糊查询
请注意观察上述两例′[国华]′的细微不同,这说明[]中的字符用逗号分隔和不用逗号分隔作用是一样的。
想一想:上述几例中的表达式,如LIKE′%[国华]%′AND sno LIKE′%[^24]′,如果不使用[]运算,你能用其他运算符来完成这些查询吗?请上机实测,检验你的想法。
(7)用ORDER BY子句对查询结果进行排序
使用SELECT语句从表中查询记录,默认情况下其结果集显示的顺序与这些记录在表中的前后顺序保持不变。实际中往往会要求查询结果按一个或多个指定列的列值排列。
SQL允许在查询语句SELECT中使用ORDER BY子句改变默认顺序以满足用户对结果排序的需求。ORDER BY子句的语法格式如下:
ORDER BY<列名1|列序号>[ASC|DESC][,<列名2|列序号>[ASC|DESC]爥]
说明:
①列名后的ASC或DESC分别表示按此列值的升序或降序对查询结果集排序,缺省表示升序。
②如果指定有多个排序列名,则首先按前列的列值排序,对前列的列值相等的记录按后续列的列值排序。
③<列序号>是指SELECT子句中列名的先后次序。
例8.16 查询选课表中已有成绩的记录,按课程号升序排列,同一课程则按分数由高到低排列。
完成此查询的SQL语句如下:
use jxgl
select sno as学号,cno as课程号,score as成绩
from sc
where score is not null
order by cno,成绩desc
go
执行结果如图8.26(b)所示。

图8.26 使用ORDER BY子句对查询进行排序
图8.26分别给出了不作排序要求的查询和按要求对结果集排序的查询结果,请你对比以理解ORDER BY子句的作用。语句中的“order by cno,成绩desc”这一子句可以写成“order by 2,3 desc”,因为在“select sno as学号,cno as课程号,score as成绩”子句中,“cno”和“score”分别位于列名表的第2、第3位。同时请你注意到order by子句中的列名还可以用SE-LECT子句中的别名。
(8)使用INTO子句将查询结果集插入新表
如前面各例,SQL默认将查询结果反馈给用户而不保存查询结果集。如果需要保存查询结果,可以在查询语句中使用INTO选项,其语法格式如下:
SELECT <列名表> INTO <新表>
FROM<数据源>
WHERE<条件>
例8.17 从选课表中将成绩90分及以上的记录写入新表Excellent。
查询语句如下:
use jxgl
select sno as学号,cno as课程号,score as成绩
INTO excellent
from sc
where score is not null and score>=90
go
执行结果如图8.27所示。提示:若报告excellent已存在,删除后再执行此语句。

图8.27 使用INTO选项将查询结果集插入新表
语句中<新表>是指当前数据库中尚不存在的表。此查询执行后,将会在数据库中生成一张基本表,其字段数据类型及宽度和NULL属性都与查询数据源一致。但是源表中字段的其他属性和约束会被忽略。此外,如果指定了别名,则它们就是新表中的字段名。
该方法常被用于建立临时表或数据表的副本。
8.2.3 统计查询和分类汇总
(1)常用聚合函数
在数据库查询中大量需要对数据进行统计计算,如统计不及格人数、计算课程平均成绩,等等。SQL Server提供了几个专门的统计函数,也称聚合函数。常用的聚合函数如下:
COUNT([DISTINCT|ALL]<列名>|倡):当指定列名时为统计指定列中列值的个数,当为“倡”时则统计记录的个数。
SUM([DISTINCT|ALL]<列名>):计算指定列列值的和。
AVG([DISTINCT|ALL]<列名>):计算指定列列值的平均值。
MAX([DISTINCT|ALL]<列名>):找出指定列列值的最大值。
MIN([DISTINCT|ALL]<列名>):找出指定列列值的最小值。
说明:
①DISTINCT表示统计时排除重复的记录,ALL表示不排除重复记录,缺省为不排除。
②对于SUM()和AVG()函数,指定的列应为数值型数据类型。
③其他还有统计标准差、方差的函数,需要时请查阅相关资料。
例8.18 统计选课人次和平均成绩,不包含尚无成绩的选课记录。
USE JXGL
SELECT COUNT(倡)AS选课人次,AVG(score)AS平均分数FROM SC
WHERE score is not null
GO
如图8.28所示为此查询的执行结果。

图8.28 统计函数的应用
想一想:本例能否不使用WHERE,而修改COUNT()的用法完成此查询?请上机实测,予以检验。
例8.19 统计选课人数、选课总人次(包含无成绩的记录)。
USE JXGL
SELECT COUNT(DISTINCT sno)AS选课人数,COUNT(倡)选课总人次
FROM SC
GO
执行结果如图8.29所示。

图8.29 DISTINCT在统计函数中的作用
(2)分组统计
分类统计是数据分析和管理工作中常见的工作。SQL查询语句使用GROUP BY子句实现分组统计,并可根据需要对查询结果集进行过滤。其语法格式如下:
SELECT <列名表> FROM <数据源>
WHERE <查询条件>
GROUP BY <列名>
[HAVING <分组检索条件>]
说明:
GROUP BY<列名>:这个子句用于指定按此<列名>进行分组。
SELECT子句中的列名如果未出现在GROUPBY子句中,则必须是以聚合函数引用的列。
WHERE子句中的<条件>仍然是检索数据源中记录行的依据。
如果有HAVING子句,则表示对经WHERE<条件>检出的分组结果再进行过滤。
例8.20 统计学生表中男生和女生各有多少人。
这是一个典型的分组统计问题,就是按性别字段的取值(只有“男”和“女”两值)分组统计。其查询语句如下:
USE JXGL
SELECT sex AS性别,COUNT(倡)AS人数
FROM student
GROUP BY sex
GO
语句执行结果如图8.30所示。

图8.30 统计男生和女生人数
例8.21 查询各系女生人数。
本例要求查询“各系”女生人数,因此分组字段应该是系,而“女生”是从表中选择记录进行统计的条件。查询执行结果如图8.31所示。
查询各系女生人数语句如下:
use jxgl
select dept系别,count(倡)女生人数from student
where sex=′女′
group by dept
go

图8.31 使用WHERE和GROUP BY查询
例8.22 查询女生人数超过两人的系部。
use jxgl
select dept系别,count(倡)女生人数from student
where sex=′女′
group by dept
having count(倡)>2
查询执行结果如图8.32所示。
本题同时使用了WHERE和HAVING条件子句,请注意区别其不同作用。
where sex=′女′针对student表中的记录,选择出“女生”分系统计人数。而having count(倡)>2是对分组后的3个统计结果(见上例)按“人数超过2人”过滤,去掉不满足这一条件的结果。
可见,可以把原题目的要求表述成“查询各系女生人数,只列出超过两人的统计结果”。

图8.32 使用HAVING子句过滤分组结果
一般而言,当查询要求的描述中包含“各……”“每……”等文字或隐含这类意思时,就是要求分组查询。你认为呢?
想一想:在SELECT语句中,并无GROUP BY子句,能否用HAVING子句选择记录?
(3)使用COMPUTE和COMPUTE BY汇总
在实际的数据库查询中,可能不仅要求查看每条记录,还希望对它们进行汇总。这时候就需要在SELECT语句中使用一个或多个COMPUTE以及COMPUTE BY子句。其语法格式如下:
COMPUTE
{{AVG|COUNT|MAX|MIN|SUM}
(<列>)}[,爥n]
[BY<列>[,爥n]]
这里的<列>必须是包含在SELECT子句中的。下面通过具体例子介绍它们的用法。
例8.23 查询课程信息详情以及总计学分、总计学时。
查询语句如下:
use jxgl
select cno课程号,cn课程名,credit学分,ct学时from course
compute sum(credit),sum(ct)
go
查询执行结果如图8.33所示。在结果显示区,前面是所有课程的记录数据,后面的两列sum则分别表示学分和学时的总计。注意:虽然在SELECT子句中为列指定了别名,但在COMPUTE子句的聚合函数中也不能使用那些别名。

图8.33 使用COMPUTE汇总
例8.24 查询学生选课详情及每个学生的平均成绩,不包含无成绩的科目。
use jxgl
select sno学号,cno课程号,score 成绩from sc
where score is not null
order by sno
compute avg(score)by sno
go
执行结果如图8.34所示,除了学生选修课程的记录,还汇总了其各门课程分数的平均值。

图8.34 使用COMPUTE BY分明细汇总
注意:
①如果要使用BY<列名>,则该查询语句必须要有ORDER BY<列名>子句,并且BY后面的列名个数要么与ORDER后的列名相同,要么是它的子集,并且顺序相同。
②在SELECT INTO语句中不能使用COMPUTE[BY]子句。
BY后面如果是多个列名,则可以使查询结果分成若干小类。
8.2.4 多表连接查询
当要查询的数据存储在不同的表里面时,这种查询就称之为连接查询。要实现多表之间的数据查询必须借助于一定的条件,称为连接条件或者连接谓语。
在SQL Server中连接查询语句的语法格式有两种:ANSI格式和SQL Server格式。
ANSI语法格式如下:
SELECT<列名表>
FROM <表1>[INNER]|OUT JOIN<表2>
ON<连接条件>
SQL Server语法格式如下:
SELECT <列名表>
FROM <表1>,<表2>
WHERE <连接条件> [AND<查询条件>]
查询分为等值连接、非等值连接、自然连接、自身连接和外连接等若干类型。
(1)等值连接、非等值连接
如果数据表的连接条件使用了比较运算符,则为等值连接或非等值连接。连接条件的一般形式如下:
[<表1>.]<列名1><比较运算符>[<表2>.]<列名2>
这里,<比较运算符>有<,<=,=,>=,>和!=。
当连接条件中的比较运算为“=”(相等)时,称为“等值连接”,使用其他5种运算符时称为“非等值连接”。连接条件中的列名就是连接字段,连接字段可以是不同的字段,但是其值必须是可比较的。
假设要求从学生表和成绩表查询学号、姓名、所选课程和成绩,DBMS是如何进行查询的?图8.35可以帮助读者从概念上理解这一查询的操作过程。显然连接条件应该是两张的学号字段值相等。
首先从学生表第一条记录开始从学号列取值“001”,然后从头开始扫描成绩表中的学号列,它的第一行学号也是“001”,符合相等的条件,因此将学生表中第一行的数据与成绩表该行的数据拼接起来成为查询结果的一行,如图8.35中①;接着继续扫描成绩表后续行的学号值,至第3行,学号值相等,把它与学生表的第一行数据连接起来,如图8.35中②;此时成绩表已扫描结束。接下来从学生表的第二行取学号值“002”,然后从头至尾扫描成绩表的学号值,没有与“002”相等的值,因此不产生连接记录;再从学生表取下一行(第3行)的学号“007”,从头至尾扫描成绩表,这一轮中第3行符合条件,于是产生一行查询记录,如图8.35中③。至此,学生表也已扫描至末尾,过程结束。

图8.35 连接查询示意图
例8.25 根据JXGL数据库中学生基本信息表和选课表的记录,查询学生已选课的情况。要求显示学号、姓名、所选课程的课程号和成绩(包括尚无成绩的记录)。
按ANSI语法的查询语句如下:
use jxgl
select student.sno,sn,sc.sno,cno,score
from student join sc on student.sno=sc.sno
go
其执行结果如图8.36所示。

图8.36 学生表和选课表的内连接查询
本例查询要求两表的连接条件是“学生表的学号与选课表的学号一致(相等)”,因此是一个等值查询。因为是内连接查询,省略了保留字中的INNER。由于学生表和选课表中都包含有学号(sno)字段,所以在出现sno列名时必须冠以表的名称,否则SQL Server将报错,如图8.37所示。

图8.37 连接的表中有同名字段时必须在列名前冠以表名
如果表的名称较长,可在FROM子句中给它们起一个别名,然后就可在SELECT子句中使用这个别名。注意:FROM子句中起别名的方式不能使用“别名=表名”这种形式。
例8.26 查询学号、姓名、所选课程代号、课程名称、成绩。
本例要求查询的数据中,学号和姓名存在于学生表,课程名只有课程信息表里面才有,而成绩记录在选课表中,因此这是一个涉及3张表的连接查询。其中,学生表与选课表通过“学号”这个公共字段连接,而选课表和课程表中通过它们的公共字段“课程号”进行连接。于是可写出查询语句如下:
use jxgl
select a.sno AS学号,sn AS姓名,b.cno AS课程号,cn课程名,成绩=score
from student a join sc b on a.sno=b.sno join course on b.cno=course.cno
go
查询执行结果如图8.38所示。

图8.38 3张表的内连接查询
本例的查询语句中只为student和sc两张表起了别名,未给第三张表course起别名。是否起别名主要看书写的语句是否更简短,当然不起别名的好处是阅读时能直接就明白是哪张表。
以上两例的连接条件都是连接字段值相等,因此都属于“等值连接”。
(2)自然连接
从例8.25的查询结果看,两张表的公共字段(学号)有重复显示。这往往是没有必要的。而例8.26中就没有包含重复的公共字段(学号和课程号)。在等值连接中,去除重复的公共字段就称为“自然连接”。
这里还注意到,对学生表、课程表而言学号和课程号分别是它们的主键,而对选课表而言学号和课程号都是它的外键。在自然连接中通常是主表和从表的连接,其连接的字段分别是主键和外键。
(3)自身连接
一般来讲,连接查询中是不同的表相互连接。但是实际应用中也会遇到相连接的表实质上是同一张表,这种情况的连接就称为“自身连接”。
例8.27 查找与学号“1102002”是同一个专业的学生。
先写出查询语句如下:
use jxgl
select b.倡from student a join student b
on a.class=b.class
where a.sno=′1102002′and b.sno!=′1102002′
go
其执行结果如图8.39所示。

图8.39 自身连接查询
因为是同一张表(student),为了避免字段引用不明确,必须给它们分别起别名(a,b)。WHERE条件语句前半段用来指明在a表中查找学号为′1102002′的记录,运算符and右边的部分是用来从b表中排除该同学(“1102002”)自己。
想一想:若要求查询与学号“1102002”不是同一个专业的学生,请写出语句并上机实测。
(4)外连接查询
上述各例连接查询其结果集仅仅包含符合连接条件的那些记录,它们又被称为内连接查询。实际应用中也会遇到既要列出符合连接条件的记录,也要列出指定表中不符合连接条件的记录。这就要用到外连接查询了。外连接查询分为左外连接、右外连接和全外连接3种。
1)左外连接(LEFTOUTER JOIN)
左外连接的结果集中除了满足连接条件的记录,还包含连接条件中JOIN关键字左侧表中所有行,而属于右侧表的那些列填充空值。
2)右外连接(RIGHTOUTER JOIN)
右外连接的结果集中除了满足连接条件的记录,还包含连接条件中JOIN关键字右侧表中所有行,而属于左侧表的那些列填充空值。
3)全外连接(FULLOUTER JOIN)
全外连接的结果集除了包含符合连接条件的记录,还包含两张连接的表中全部行。
例8.28 查询“电子信息”系的选课情况。包括学生学号、姓名、班级及该学生选修课程的课程号和成绩,没有选课的学生也要显示他们的学号、姓名、班级。
显然这个查询同时涉及学生表和选课表的列,要用连接查询。此外,它要求既要显示选修了课程的学生的信息,也要显示未选课程的学生的信息,因此要用外连接查询。以下是一种可实现此查询要求的SQL语句:
select student.sno学号,sn姓名,class班级,cno课程号,score成绩
from student left outer join sc
on student.sno=sc.sno
where dept=′电子信息′
go
查询执行结果如图8.40所示。

图8.40 左外连接查询
结果中课程号、成绩均为NULL的行就表示无选课信息的学生,即左表中不符合连接条件(右表中没有他们的学号)的记录也被查询了出来。
例8.29 查询各门课程被选修的情况。要求显示课程号、课程名、学号、成绩,包括有学生选修和无学生选修的全部课程。
查询语句如下:
use jxgl
select course.cno课程号,cn课程名,sno选修学生学号,score成绩
from sc RIGHT JOIN course ON sc.cno=course.cno
go
执行结果如图8.41所示。

图8.41 右外连接查询举例
本例结果中第6行,来自选课表的选修学生学号和成绩均为空值(NULL),这就是一门没有学生选修的课程。值得指出的是,虽然来自课程表的“课程号”和“课程名”两列看起来处在结果表的左侧,但是在连接条件子句中,它们位于JOIN的右侧,因此要用RIGHT JOIN。最后从本例可知,LEFTOUTER JOIN和RIGHTOUTER JOIN中的OUTER可以省略。
8.2.5 嵌套查询
通过前面的学习可知,SQL的查询中SELECT爥FROM爥WHERE爥是其最基本的结构,称为一个查询块。把一个查询块放置在另一个查询块的WHERE或HAVING子句中作为条件,这样就构成了查询的嵌套结构,称其为嵌套查询。嵌入WHERE或HAVING子句中的查询块称为内层查询或子查询,而被嵌套了子查询的查询块称为外层查询或父查询。SQL允许多层嵌套。从结构上看,外层查询在前,内层查询在后;而执行顺序一般是先内后外。常常使用IN运算符、比较运算符或EXISTS把子查询嵌入父查询中。
(1)使用IN运算符的子查询
IN是集合运算,其格式如下:
<表达式>[NOT]IN(子查询)
其含义是检测<表达式>的值是否存在于子查询的结果集中。
例8.30 查询选修了课程但还未取得成绩的学生的学号、姓名、所在班级。
查询语句如下:
use jxgl
select sno学号,sn姓名,class所在班级from student
where sno IN(SELECT SNO FROM SCWHERE SCORE ISNULL)
go
子查询的作用是从选课表查出尚无成绩的全部学号,父查询再以这些学号从学生表中查询他们的学号、姓名、班级。查询执行结果如图8.42所示。

图8.42 使用IN的子查询
(2)使用比较运算符的子查询
使用比较运算符的子查询是指用>,>=,=,<,<=,!=表达父查询的条件与子查询结果(或结果集)之间的关系,其使用格式如下:
WHERE<表达式>比较运算符[ANY|ALL](子查询)
如果子查询的结果不是一个值,则要使用ANY或ALL关键字。否则可以不使用ANY或ALL关键字。
ALL:表达式与子查询每一个结果相比较都满足关系,才返回True。
ANY:表达式与子查询中某一个结果相比较能满足关系,就返回True。
实际上,ALL实现的是“AND(与)”运算,如“成绩>ALL(60,80,90)”等价于“成绩>60 and成绩>80 and成绩>90”;而ANY实现的是“OR(或)”运算,如“年龄<ANY(18,21,25)”等价于“年龄<18 or年龄<21 or年龄<25”。
例8.31 某生学号为1101002,查询比他所有成绩都高的学生成绩记录。
select 倡from sc
where score>ALL(SELECT SCORE FROM SCWHERE SNO=′1101002′)
go
执行结果如图8.43所示,可见只有一个同学的C05课程成绩比1101002的各科成绩都高。

图8.43 使用ALL的子查询
例8.32 查询成绩高于C03课程最低分的选课成绩记录。
select倡from sc
where score>ANY(SELECTSCORE FROM SCWHERE CNO=′C03′)and CNO!=′C03′
GO
执行结果如图8.44左图所示,如图8.44右图所示为C03课程的选课记录供对照。

图8.44 使用ANY的子查询
想一想:本例父查询的where子句中“and CNO!=′C03′”起什么作用?
(3)使用EXISTS的子查询
EXISTS用来测试子查询是否有结果,即结果集是否为空集。只有子查询的结果集不为空才返回TURE。
也可以使用NOT EXISTS,其含义正好与EXISTS相反,即子查询无结果为逻辑TRUE。
因为只需要测试子查询是否有结果,而不用关心查询的数据,所以子查询的列名表通常用“倡”即可。
例8.33 查询无任何学生选修的课程信息。
查询语句如下:
use jxgl
select倡from course
where not exists
(select倡from sc where cno=course.cno)
go
其执行结果如图8.45所示。

图8.45 使用EXISTS的子查询
例子中,子查询的含义是按照与父查询相同课程号查找记录。按照not exists的含义,从选课表查不到该课程号的选修数据时返回“真”,从而显示这个课程号对应的课程信息。
8.2.6 视图
视图也是数据库中的对象,它用于定义对数据表的查询。视图是给用户设计出来的一种对数据表中的数据进行观察和操作的窗口,因此视图把基本表和用户进行了隔离,保护了数据库的安全性。
从组织结构看视图也是一个二维表,其数据取自于一个或几个基本表或其他视图。正因为如此,它只是一种虚拟表,其中并无数据存储。
视图由数据库设计人员根据用户需求设计,在DBMS下创建并存储在用户数据库中,可见应用视图包含分析设计、创建、管理维护和利用等几个过程。
(1)视图的分析和设计
设计视图的目的主要是简化查询和操作数据、为不同用户定制所需数据、提高数据库的安全性。
①对于复杂的查询任务,如复杂的多表连接查询、复杂的子查询、频度极高的查询等,应该建立视图,达到以此建立长期利用、简化工作的目的。
②在数据库系统中,不同用户有不同的数据需求,比如学生希望看到的是班级课表、教师则仅关心自己的课表(教师课表)、而教室安排和管理人员关注的是教室课表。根据需求为不同用户设计视图,给他们定制数据。
③用户通过视图只能查询和修改他们看得见的数据。因此,应该根据用户的访问权限为他们量身定制视图,实现数据库中信息的安全、保密要求。
值得指出的是,视图的应用也有一定局限,甚至可能降低数据库的运行效率。必要时,请查阅相关资料。
(2)视图的创建
视图创建就是按照设计在DBMS中把它们建立起来。在SQL Server环境下定义视图有可视化操作方式和使用T-SQL语句两种方式。可视化方法请参阅联机教程等资料。这里只介绍创建视图的T-SQL语句。
创建视图语句基本格式如下:
CREATE VIEW<视图名>[列名表]
[WITH ENCRYPTION]
AS SELECT语句
[WITH CHECK OPTION]
其中:[SCHEMABINDING]
<列名表>:一般只用在计算列或指定一个与来源列不同的名称时。
AS:语句保留字。
SELECT语句:定义视图的查询语句,即定义该视图的数据如何形成。不允许使用COM-PUTE[BY]子句。不能使用ORDER BY子句,除非在SELECT语句的选择列表中也有一个TOP子句。
WITH CHECK OPTION:强制通过视图对数据进行的修改必须符合在查询语句中定义的条件。
ENCRYPTION:对视图加密。对系统表中包含CREATE VIEW语句文本的条目进行加密,从而不能查看定义该视图的SQL文本。
SCHEMABINDING:将视图绑定到基础表的架构。如果指定了SCHEMABINDING,则不能以将影响视图定义的方式修改基表或表。
关于视图创建的更多说明请参考相关资料。
例8.34 创建能查询学号、姓名、性别、班级、课程名、学分、成绩等信息的视图。
use jxgl
go
CREATE VIEW v_S_C_SC
AS
select s.sno学号,sn姓名,sex性别,class班级,cn课程名,ct学分,score成绩
from student s join sc on s.sno=sc.sno join course c on sc.cno=c.cno
go
语句执行如图8.46所示。创建了这个视图,以后就能通过它查询、修改存储在3张基本表的数据了。

图8.46 创建视图
例8.35 创建80分以下学生选课记录的视图,要求加密、检查,命名为v_scl80。SQL语句如下:
use jxql
go
create view v_scl80
with encryption
as
select倡from sc
where score<80
with check option
go
语句执行如图8.47所示。

图8.47 创建附带加密、检查选项的视图
(3)视图管理和维护
视图管理包括查看视图信息、修改视图、删除视图等工作。
查看视图属性等信息,可以直接在对象资源管理器中右键单击视图名打开快捷菜单。选择相应的子菜单可查看、修改其基本属性、依赖关系,可编辑脚本,等等,如图8.48所示。

图8.48 在对象资源管理器中查看视图属性
也可执行sp_help<视图名>和sp_depends<视图名>分别查看指定视图的基本属性和依赖关系,如图8.49所示。

图8.49 使用命令查看视图的属性和依赖关系
执行sp_helptext<视图名>命令可查看视图的定义文本,如图8.50所示。

图8.50 使用sp_helptext查看视图的定义文本
想一想:为什么图8.50左图和右图显示结果不一样?
(4)视图使用
例8.36 通过视图v_s_c_sc查询11网络班的同学成绩为70~89的数据。
select倡from v_s_c_sc
where班级=′11网络′and成绩between 70 and 89
go
查询结果如图8.51所示。

图8.51 通过视图查询数据
例8.37 通过视图v_s_c_sc修改学号1102005同学的班级为“11软件”。
本例语句及执行结果如图8.52所示。

图8.52 通过视图修改数据,查看修改结果
8.3 任务小结
本次任务按照前期学生成绩管理数据库对数据查询的需求,创建了一系列视图。视图是用户端的数据视图,可以为不同用户定制数据查询和数据操作界面;视图可以隔离用户和基本表,是一个简单而有效的数据安全机制。
查询数据是数据库建设的根本目的。本次任务对数据查询、视图建立和使用等相关基本知识作了较为系统的介绍。
8.4 练习与实训
(1)填空题
①SQL查询语句中,SELECT 主要语句用于_____,实现_____关系运算,_____保留字用来指定仅返回结果集中前面若干行,_____关键字用来去除重复的行。
②在查询语句中为表和列指定别名的方式有_____、_____。
③查询语句中FROM 子句中主要包括_____或_____名称,其作用 是_____。
④WHERE 子句的作用是________,HAVING子句的作用 是________。
⑤按某个字段分组查询,要使用_____子句。
⑥要对查询结果排序,要使用_____子句。分别用_____指定升序,_____指定降序,如果没有指明则按照_____排序。
⑦SQL中常用的几个聚合函数是:_____用于统计记录条数,_____用于计算列的和,_____用于计算列的平均值,_____用于找出列的最大值,用于找出列的最小值。SUM()和AVG()只适用于_____类型的列。
⑧ANSI 语法中连接条件的一般形式是_____。
⑨COMPUTE 子句的作用是_____。
⑩不能精确表达条件的查询称为_____查询,它使用关键字_____和通配符构成条件表达式。通配符%表示_____,通配符_____(下画线)表示_____,符号[]表示_____。
⑪_____表示(非)空值,BETWEEN 0 AND 100等价于_____。
⑫嵌套查询语句中,子查询前面的运算符有_____、_____、_____。
(2)问答题
①简述视图与基本表之间的关系。
②什么是单表查询?什么是连接查询?
③简述等值连接、自然连接、自身连接的特征。
④内连接和外连接的区别是什么?
(3)操作题
①针对学生成绩管理数据库完成以下各项查询任务:
a.查询学生选课表中的全部数据。
b.查询计算机系学生的姓名、年龄。
c.查询成绩为70~80分的学生的学号、课程号和成绩。
d.查询计算机系年龄为18~20且性别为“男”的学生姓名和年龄。
e.查询课程号为“C01”的课程的最高分数。
f.查询计算机系学生的最大年龄和最小年龄。
g.统计每个系的学生人数。
h.统计每门课程的选课人数和考试最高分。
i.统计每门课程的选课门数和考试总成绩,并按选课门数的升序显示结果。
j.查询总成绩超过200分以上的学生,要求列出学号、总成绩。
k.查询选修课程号为“C02”的学生的姓名和所在系。
l.查询成绩在80分以上的学生的姓名、课程号和成绩,并按成绩的降序显示结果。
m.查询哪些课程没有人选修,要求列出课程号和课程名。
n.用子查询实现以下查询:
查询选修了课程号为“C01”的学生姓名和所在系。
查询数学系成绩在80分以上的学生的学号和姓名。
查询计算机系考试成绩最高的学生姓名。
o.创建视图v_01,该视图从学生表和选课表中查询学号、姓名、系别、班级、课程号、成绩。
p.通过视图v_01将成绩为空的记录复制到新表sc50中。
q.通过视图v_01将课程号为“C01”的成绩全部增加10分。
②为华龙社区图书室设计并创建分别适用于管理员、读者的视图。