3.4.1 单表查询

3.4.1 单表查询

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

(1)选择表中的若干属性列

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

【例3.24】 查询所有课程的编号和名称。

SELECT Cno,Cname

FROM Course;

可能的查询结果为:

【例3.25】 查询全体课程的详细记录。

SELECT*

FROM Course;

等价于

SELECT Cno,Cname,Cpno,Ccredit

FROM Course;

如果要将表中的所有属性列都列出来,且属性列按照定义基本表的顺序显示,则可以简单地将select_list指定为星号(*)。

【例3.26】 查询全体学生的姓名、年龄。

SELECT Sname,YEAR(GETDATE())-YEAR(Sbirthday),岁

FROM Student;

假设当前年份为2021年,则可能的查询结果为:

SELECT子句的select_list不仅可以是表中的属性列,也可以是字符串常量、函数等,还可以使用算术运算符或者逻辑运算符。此外,用户还可以通过指定别名来改变查询结果的列标题,这对于含算术表达式、常量、函数名等的目标列表达式尤为有用。例如对于例3.26可以定义如下列别名:

SELECT Sname AS姓名,YEAR(GETDATE())-YEAR(Sbirthday)AS年龄,岁AS岁

FROM Student;

可能的查询结果为:

在某个目标列表达式后,可以使用AS关键字为其指定别名,AS关键字可以省略。

【例3.27】 查询有学生选修过的课程编号。

SELECT Cno

FROM SC;

可能的查询结果为:

该查询结果包含了重复的行,如果希望去掉重复的元组,则须指定DISTINCT关键字:

SELECT DISTINCT Cno

FROM SC;

则可能的查询结果为:

如果没有指定DISTINCT,则默认为ALL,即保留结果表中的重复元组。

(2)选择表中的若干元组

一个基本表中往往包含大量数据,可能有时需要查询表中的全部元组或部分元组。查询满足指定条件的元组可以通过WHERE子句实现。WHERE子句常用的查询条件见表3-9。

表3-9 WHERE子句常用的查询条件

【例3.28】 查询“计算机”学院所有学生的姓名。

SELECT Sname

FROM Student

WHERE Sdept=计算机;

关系数据库管理系统执行该查询的执行过程是:对Student表进行全表扫描,依次取出每一个元组,检查该元组在Sdept上的值是否等于“计算机”,如果相等,则取出Sname列的值形成一个新的元组添加到结果关系中,最后输出结果关系。

如果Student表中的记录比较多,且已经在Student表的Sdept属性列上建立了索引的话,系统会利用该索引找出Sdept属性列上取值为“计算机”的元组,从中取出Sname值形成结果关系。

【例3.29】 查询成绩为80~90分(包括80和90)的学号。

SELECT DISTINCT Sno

FROM SC

WHERE Grade BETWEEN 80 AND 90;

注意:BETWEEN AND前可以加关键字NOT,表示指定范围之外的值。

【例3.30】 查询“会计”“计算机”“金融”学院的学生姓名。

SELECT Sname

FROM Student

WHERE Sdept IN(会计,计算机,金融);

除了可以用“=”“!=、<>”实现字符串的精确匹配,实现精确查询以外,在很多情况下,要执行模糊查询的任务,比如不知道学生的全名,只知道学生姓“王”。这种情况下,关键字LIKE可以用来进行字符串的模糊匹配,其基本格式如下:

[NOT]LIKE匹配串[ESCAPE换码字符]

其含义是查找指定的属性列值与“匹配串”相匹配的元组。“匹配串”可以是一个完整的字符串,也可以包含通配符:百分号(%)和下画线(_):

百分号(%):代表任意长度(长度可以为0)的字符串。例如“a%b”表示以a开头、以b结尾的任意长度的字符串。如ab、acb、acccb等都满足该匹配串;

下画线(_):代表任意单个字符。

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

【例3.31】 查询所有姓“张”的学生的姓名和性别。

SELECT Sname,Ssex

FROM Student

WHERE Sname like张%;

注意:该例中的like执行了模糊查询,此处的like不能用“=”代替。

【例3.32】 查询以“DB_”开头,且倒数第3个字符为“i”的课程的详细情况。

SELECT*

FROM Course

WHERE Cname LIKE DB\_%i__ESCAPE\;

注意:这里的匹配串为“DB\_%i__”。ESCAPE\表示“\”为换码字符。匹配串中紧跟在换码字符“\”后面的字符“_”不再具有通配符的含义,而是转为普通的“_”字符。而“i”后面两个“_”的前面均没有换码字符“\”,所以它们仍作为通配符。

【例3.33】 查询没有选修课的课程编号和名称。

注意:创建基本表时,可以指定某属性列是否可以取NULL(空值)。NULL一般表示数据未知、不适用或将在以后添加,可以使用ISNULL或ISNOT NULL判断某属性列取值是否为NULL,IS不能用“=”替换。

【例3.34】 查询选修了课程号为“C001”且成绩大于等于60分的学号。

SELECT Sno

FROM SC

WHERE Cno=C001 AND Grade>=60;

注意:逻辑运算符AND和OR可以用来连接多个查询条件。AND的优先级高于OR。

例3.30也可以使用下述语句实现:

SELECT Sname

FROM Student

WHERE Sdept=会计OR Sdept=计算机OR Sdept=金融;

(3)排序查询

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

【例3.35】 查询选修了“C001”课程的学号及成绩,查询结果按成绩的降序排序。

SELECT Sno,Grade

FROM SC

WHERE Cno=C001

ORDER BY Grade DESC;

由于Grade是select_list中的第二个目标列表达式,因此ORDER BY后的Grade可以用其在select_list中的序号“2”代替,上述语句可以更改为:

SELECT Sno,Grade

FROM SC

WHERE Cno=C001

ORDER BY 2 DESC;

【例3.36】 查询选修了“C001”课程,且排名在前10的学生的学号及成绩。

SELECT TOP 10 Sno,Grade

FROM SC

WHERE Cno=C001

ORDER BY Grade DESC;

注意:在SELECT语句中使用TOP n,可以指定只从查询结果集中输出前n行;如果使用TOP n PERCENT,则只从结果集中输出前百分之n行。

(4)聚合函数

为了进一步方便用户,增强查询功能,SQL提供了许多聚合函数,可以对获取的数据进行分析和报告。常用的聚合函数见表3-10。

表3-10 常用的聚合函数

如果指定了DISTINCT关键字,则表示在计算时要取消指定属性列中的重复值。如果不指定DISTINCT关键字或指定ALL关键字(ALL为默认值),则表示不取消重复值。

注意:WHERE子句中是不能使用聚合函数作为条件表达式的。聚合函数可以用于SELECT子句和HAVING子句。

【例3.37】 查询选修了课程的学生人数。

SELECT COUNT(DISTINCT Sno)AS选课人数

FROM SC;

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

【例3.38】 计算选修“C001”课程的最高分、最低分和平均分。

SELECTMAX(Grade)AS最高分,MIN(Grade)AS最低分,AVG(Grade)AS平均分

FROM SC

WHERE Cno=C001;

(5)分组查询

分组查询是对数据按照一个或多个属性列进行分组。SELECT语句中可以使用GROUP BY子句将查询结果进行分组,值相等的为一组。对查询结果分组是为了细化聚合函数的作用对象。分组后聚合函数将作用于每一个组,即每一组都会返回一个函数值。

【例3.39】 查询每门课程的课程号及该课程的最高分、最低分。

SELECT Cno AS课程号,MAX(Grade)AS最高分,MIN(Grade)AS最低分

FROM SC

GROUP BY Cno;

该语句对查询结果按Cno的值分组,所有具有相同Cno值的元组为一组,然后对每一个组作用聚合函数MAX和MIN进行计算,以求得该组的最高分和最低分。使用了GROUP BY子句后,SELECT子句中只允许出现聚合函数及作为分组依据的各属性列。

可能的查询结果为:

【例3.40】 查询学院人数超过100人的学院名称及人数。

SELECT Sdept AS学院,COUNT(*)AS人数

FROM Student

GROUP BY Sdept

HAVING COUNT(*)>100;

这里先用GROUP BY子句按Sdept进行分组,再用聚合函数COUNT对每一组计数;HAVING短语给出了筛选组的条件,只有满足条件(即元组个数>100,表示此学院的人数超过100人)的组及人数才会被列出来。

【例3.41】 查询最低分大于等于85分的学生学号。

SELECT Sno

FROM SC

GROUP BY Sno

HAVING MIN(Grade)>=85;

因为WHERE子句中不能使用聚合函数作为条件表达式,所以不能使用下面的语句:

SELECT Sno

FROM SC

WHERER MIN(Grade)>=85

GROUP BY Sno;

【例3.42】 查询每个学院男女生的人数,并显示学院名称及男女生人数。

SELECT Sdept学院名称,Ssex性别,COUNT(*)人数

FROM Student

GROUP BY Sdept,Ssex;

作为分组的属性列可以有多个,本例首先按Sdept分组,Sdept取值相同的元组分为一组,然后对分得的每一组再按Ssex分组,即学院名称Sdept相同的为一大组,每一大组再按性别Ssex分为若干小组,最后COUNT聚合函数作用于最小的组。

【例3.43】 查询不及格的课程数量在3门及以上的学生学号及不及格课程数,查询结果按学号升序排序。

SELECT Sno AS学号,COUNT(*)AS不及格课程数

FROM SC

WHERE Grade<60

GROUP BY Sno

HAVING COUNT(*)>=3

ORDER BY Sno ASC;

其执行过程是:从FROM子句指定的SC表中,筛选满足WHERE子句的元组,然后将元组按Sno分组,对于分得的每组应用COUNT聚合函数,只有满足HAVING短语的组中取Sno及COUNT函数值输出,输出时按ORDER BY子句中的属性列Sno的升序输出。

注意:WHERE子句与HAVING短语的区别在于作用对象不同。WHERE子句作用于基本表或视图,从中选择满足条件的元组;HAVING短语作用于组,从中选择满足条件的组。

(6)将查询结果生成一个新表

可以使用INTO子句将查询结果生成一个新表或存放在一个临时表中。如果要将查询结果存放在临时表中,则临时表名需以“#”为前缀。

【例3.44】 查询学生表中“计算机”学院的学生信息,并将结果保存到表Jsj中。

SELECT*INTO Jsj

FROM Student

WHERE Sdept=计算机;

【例3.45】 查询学生表中女生的信息并保存到临时表Temp中。

SELECT*INTO#Temp

FROM Student

WHERE Ssex=女;