目录
6.1 连接查询
6.1.1 交叉连接(cross join)
6.1.2 内连接(inner join)
6.1.3 外连接(outer join)
6.1.3.1 左外连接(left [outer] join)
6.1.3.2 右外连接(right [outer] join)
6.1.4 自连接(cross join)
6.2 嵌套查询
6.2.1 单值嵌套查询
6.2.2 多值嵌套查询
6.2.2.1 使用in运算符
6.2.2.2 使用比较运算符的子查询
6.2.2.3 使用any、all运算符
6.2.2.4 使用EXISTS连接的子查询
6.3 集合查询
6.3.1 union形成并集
6.3.2 except形成差集
小结:
6.1 连接查询
数据库本着精简的设计原则,通常将数据存放于不同的表中,最大限度地减少数据冗余。在实际应用中,经常需要从多个数据表中查询满足一定条件的记录,这时就要用到连接查询。
连接查询分为交叉连接、内连接、外连接和自连接。
6.1.1 交叉连接(cross join)
交叉连接返回被连接表中所有数据行的笛卡尔积,
查询结果集的总行数=被连接表行数的乘积,总列数=被连接表列数的总和。
其基本语法格式如下:
SELECT * FROM 表名1 CROSS JOIN 表名2
在from子句中也可以省略cross join,使用逗号分隔被连接的表,
其基本语法格式如下:
SELECT * FROM 表名1 , 表名2
交叉连接产生的结果集一般没有实际应用的意义,所以这种连接很少使用。
6.1.2 内连接(inner join)
内连接是最常使用的连接查询方式,通过inner join或者join连接两个表,结果集中只包含满足连接条件的记录。连接条件通常采用“主键 = 外键”的形式。
内连接创建连接关系有以下两种方式:
①在WHERE子句中创建连接关系,两个表名出现在FROM子句中,其基本语法格式如下:
SELECT 列名列表
FROM 表名1 , 表名2 WHERE 表名1.列名 = 表名2.列名
② 在FROM子句中创建连接关系,其基本语法格式如下:
SELECT 列名列表
FROM 表名1 [ INNER ] JOIN 表名2 ON 表名1.列名 = 表名2.列名
【例3.102】查询所有选课学生的学号、所选课程的名称和成绩。
SELECT score.cno, cname, grade
FROM course, score
WHERE course.cno = score.cno; /*在WHERE子句中给出等值连接查询条件*/
【例3.103】查询男学生的选课情况。要求列出学号、姓名、性别、课程名、课程号和成绩。
-- 方法1
SELECT A.sno, A.sname, A. ssex, C.cname, C.cno, B.grade
FROM student A INNER JOIN score B ON A.sno = B. sno /*可省略INNER*/
INNER JOIN course C ON B.cno = C.cno
WHERE (A.ssex = '男') ;
-- 方法2
SELECT A.sno, A.sname, A. ssex, C.cname, C.cno, B.grade
FROM student A, score B, course C
WHERE A.sno = B. sno AND B.cno = C.cno AND A.ssex = '男';
6.1.3 外连接(outer join)
在内连接查询中,结果集中只包括满足连接条件的数据行,但有时用户也希望在结果集中能显示那些不满足连接条件的数据,这就需要使用外连接查询。
外连接操作的类型可分为左外连接和右外连接。
在创建外连接时,表在SQL语句中出现的顺序非常重要。
出现在JOIN左边的表是“左表”,出现在JOIN右边的表是“右表”。
6.1.3.1 左外连接(left [outer] join)
左外连接指定在结果集中除了包括由内连接返回的所有行之外,还包括左表中所有不满足连接条件的行,并将结果集中右表的输出列设置为NULL。
因此,左外连接可以使得左表中的所有记录都显示在结果集中。
左外连接是对连接条件中左边表不加限制,其基本语法格式如下:
SELECT 列名列表
FROM 表名1 LEFT [OUTER] JOIN 表名2 ON 表名1.列名 = 表名2.列名
【例3.102】student表左外连接score表。
SELECT A.sno, A.sname, B.cno, B.grade
FROM student A LEFT OUTER JOIN score B ON A.sno = B.sno;
6.1.3.2 右外连接(right [outer] join)
右外连接指定结果集中除了包括由内连接返回的所有行之外,还包括右表中所有不满足连接条件的行,并将结果集中左表的输出列设置为NULL。
因此,右外连接可以使得右表中的所有记录都显示在结果集中。
右外连接是对连接条件中右边表不加限制,其基本语法格式如下:
SELECT 列名列表
FROM 表名1 RIGHT [OUTER] JOIN 表名2 ON 表名1.列名 = 表名2.列名
【例3.107】teacher表右外连接lesson表。
SELECT A.tno, A.tname, B.*
FROM teacher A RIGHT OUTER JOIN lesson B ON A.tno = B.tno;
6.1.4 自连接(cross join)
自连接是指一个表自己与自己建立连接,也称为自身连接。
若要在一个表中找具有相同列值的行,则可以使用自连接。
使用自连接时需要为表指定两个别名,且对所有列的引用均要用别名限定。
【例3.101】查询选修“数据库原理”(课程号01301)课程的成绩高于学号为202301020101的学生的成绩的所有学生信息,并按成绩从高到低排列。
SELECT x.*
FROM score x , score y /*将成绩表score分别取别名为x和y*/
WHERE x.cno= '01301' and x.grade > y. grade and
y.sno= '202301020101' and y.cno= '01301'
ORDER BY x. grade DESC;
多学一招:
在连接查询时,若数据表连接的字段同名,则连接时的匹配条件可以使用using代替on。
基本语法格式如下:
SELECT 列名列表
FROM 表名1 [ inner | left | right ] join 表名2
using(列名)
【例3.103】查询男学生的选课情况。要求列出学号、姓名、性别、课程名、课程号和成绩。
SELECT A.sno, A.sname, A. ssex, C.cname, C.cno, B.grade
FROM student A JOIN score B USING(sno)
INNER JOIN course C USING(cno)
WHERE (A.ssex = '男') ;
注意:
如果连接的表中有相同的列名,要求加上表名或表别名作为前缀来限定列名,即用“表名.列名”或“表别名.列名”表示,明确列名来自哪个数据表,否则系统将无法执行此查询,并提示错误信息。表别名往往是一个缩短了的表名,如果定义了表别名,则不能使用表名。如果列名不重名,可以不加表名或表别名前缀。
连接查询,需要根据要解决的问题进行分析
- 结果来自哪几个表(确定连接表)?
- 表之间建立怎样的连接(确定连接类型)?
- 选取怎样的数据(SELECT 字段名)?
只有将这些问题搞清楚,才可能写出正确的语句。
6.2 嵌套查询
在SQL语言中,一个SELECT … FROM … WHERE语句称为一个查询块,将一个查询块嵌套在另一个查询块中的查询称为嵌套查询。
在嵌套查询中,上层查询块称为外层查询或父查询,下层查询块称为内层查询或子查询。
SQL语言允许多层嵌套查询,即一个子查询中还可以嵌套其他子查询。
嵌套查询一般按照由里向外的顺序处理,即先处理最内层的子查询,然后一层一层向外处理,直到最外层查询块。
需要注意以下几点:
- 子查询语句必须用圆括号括起来。
- 子查询中不能使用ORDER BY子句,ORDER BY子句只能对最终查询结果进行排序。
- 有些嵌套查询可以用连接查询替代
6.2.1 单值嵌套查询
单值嵌套查询是指子查询返回一个单一的值(即标量)。
【例3.109】查询选修“数据库原理”课程的所有学生的学号和成绩。
-- 方法1
SELECT sno, grade
FROM score
WHERE cno=(
SELECT cno
FROM course
WHERE cname='数据库原理');
-- 方法2
ELECT S.sno, S.grade
FROM score S JOIN course C USING(cno)
WHERE cname='数据库原理';
6.2.2 多值嵌套查询
多值嵌套查询是指子查询返回一个结果集,这个结果集包含多行数据。
这种子查询通常与any、all、in、not in、exits、not exits等操作符一起使用。
6.2.2.1 使用in运算符
IN 是属于的意思,等价于“=ANY”,即等于子查询结果集中的任何一个值。
【例3.110】查询与“陈嘉宁”在同一个班级学习的学生。
-- 方法1
SELECT sno, sname, clsno
FROM student
WHERE clsno IN -- in可以换成=
(SELECT clsno
FROM student
WHERE sname= '陈嘉宁')
-- 方法2
SELECT S1.sno, S1.sname, S1. clsno
FROM student S1 JOIN student S2 USING(clsno)
WHERE S2.sname = '陈嘉宁'
【例3.111】查询选修了课程名为“数据结构”课程的学生的学号和姓名。
-- 方法1
SELECT sno, sname
FROM student
WHERE sno IN (
SELECT sno
FROM score
WHERE cno IN (
SELECT cno
FROM course
WHERE cname= '数据结构'
)
)
-- 方法2
SELECT student.sno, sname
FROM student, score, course
WHERE student.sno = score.sno AND score.cno = course.cno AND course.cname= '数据结构'
6.2.2.2 使用比较运算符的子查询
带有比较运算符的子查询是指父查询与子查询之间用比较运算符进行连接。
当用户确切知道内层查询返回单个值时,可以用>、<、=、>=、<=、!=或<>等比较运算符。
【例3.112】查询与“陈嘉宁”在同一个班级学习的学生。
SELECT sno, sname, clsno
FROM student
WHERE clsno =
(SELECT clsno
FROM student
WHERE sname= '陈嘉宁')
【例3.113】找出每个学生超过他选修课程平均成绩的课程号。
SELECT cno
FROM score x
WHERE grade >= (
SELECT AVG (grade)
FROM score y
WHERE y.sno=x.sno)
6.2.2.3 使用any、all运算符
子查询返回单值可以用比较运算符,但返回多值时要用ANY(SOME)或ALL谓词修饰符。
而使用ANY或ALL谓词的时候必须同时使用比较运算符。 其基本语法格式如下:
expression {> | >= | = | < | <= | <> | != | <=> } { ALL | ANY | SOME } ( subquery )
【例3.114】查询其他班级中比“2023计科1班”某一学生年龄小的学生的姓名和出生日期。
1.首先找出“2023计科1班”中所有学生的年龄,构成一个集合
2.找所有不是“2023计科1班”且年龄小于上述集合中任一值的学生
SELECT sname, sbirthday
FROM student
WHERE sbirthday < ANY ( SELECT sbirthday
FROM student
WHERE clsno =( SELECT clsno
FROM class
WHERE clsname= '2023计科1班')
)
AND clsno<> ( SELECT clsno
FROM class
WHERE clsname= '2023计科1班'
)
ANY和ALL谓词有时可以用集函数实现
ANY与ALL与集函数的对应关系
用集函数实现子查询通常比直接用ANY或ALL查询效率要高,
因为前者通常能够减少比较次数
练习: 找出比女生年龄都小的学生的学号、姓名和性别。
-- 方法1
select sno,sname, ssex
from student
where sbirthday > all (
select sbirthday
from student
where ssex ='女' )
-- 方法2
select sno,sname, ssex
from student
where sbirthday > (
select max(sbirthday)
from student
where ssex ='女' )
6.2.2.4 使用EXISTS连接的子查询
EXISTS和NOT EXISTS关键字用来确定数据是否在子查询结果集中存在。
exists表示子查询至少返回一行时条件成立,
not exists表示子查询中没有任何记录返回时条件成立
使用exists连接子查询时,相当于对外部查询的数据行进行了一次存在测试。
外部查询的where子句测试满足子查询的行是否存在。
带有exists的子查询不返回任何数据行,它只返回true或false。
【例3.118】查询教师工号为“01001”的教师讲授的课程的课程号、课程名和学时。
SELECT cno, cname, chour
FROM course
WHERE EXISTS (
SELECT * FROM lesson WHERE cno=course.cno AND tno='01001');
SELECT cno, cname, chour
FROM course
WHERE cno IN (
SELECT cno FROM lesson WHERE tno='01001');
SELECT cno, cname, chour
FROM course
WHERE '01001' IN (
SELECT tno FROM lesson WHERE cno=course.cno);
SELECT DISTINCT course.cno, cname, chour
FROM course INNER JOIN lesson USING(cno) WHERE tno='01001';
【例3.119】查询还没有学生选课的课程信息。
SELECT * FROM course A
WHERE NOT EXISTS
(SELECT cno FROM score WHERE cno=A.cno);
【练习】 查询没有选修01101号课程的学生学号、姓名。
SELECT sno, sname
FROM student
WHERE NOT EXISTS(SELECT *
FROM score
WHERE score.sno = student.sno AND cno='01101');
【练习】查询选修全部课程的学生姓名。
-- 方法1
SELECT sname
FROM student
WHERE NOT EXISTS (SELECT * FROM course
WHERE NOT EXISTS (SELECT * FROM score
WHERE sno= student.sno
AND cno= course.cno
)
);
-- 方法2
select sname
From student
Where sno IN(select sno
from score
Group by sno
Having count(*) = (select count(*) from course)
)
6.3 集合查询
集合运算符将来自两个或多个查询的结果合并到单个结果集中。T-SQL支持三种集合运算:并集(UNION)、交集(INTERSECT)、差集(EXCEPT)。
集合运算的限定条件如下:
(1)子结果集要具有相同的结构。
(2)子结果集的列数必须相同。
(3)子结果集对应的数据类型必须可以兼容。
(4)每个子结果集不能包含ORDER BY和COMPUTE子句。
集合运算的语法如下:
select_statement
集合运算符
select_statement
[集合运算符] select_statement … ]
6.3.1 union形成并集
【例3.120】查询“2023计科1班”的学生或2005年以后出生的学生。
SELECT *
FROM student
WHERE clsno= '2023010101'
UNION
SELECT *
FROM student
WHERE YEAR (sbirthday) > 2005;
SELECT DISTINCT *
FROM student
WHERE clsno= '2023010101' OR YEAR (sbirthday) > 2005;
6.3.2 except形成差集
【例3.122】查询“2023计科1班”的学生与2005年以后出生的学生的差集。
SELECT *
FROM student
WHERE clsno= '2023010101'
EXCEPT
SELECT *
FROM student
WHERE YEAR (sbirthday) > 2005;
SELECT DISTINCT *
FROM student
WHERE clsno= '2023010101' AND YEAR (sbirthday) <= 2005;
小结:
实施查询任务,可按照以下步骤进行分析,并逐步实现。
步骤1:分析查询涉及的表,包括查询条件和查询结果涉及的表,确定是单表查询,还是多表查询,确定FROM子句中的表名。
步骤2:如果是多表查询,分析确定表与表之间的连接条件,即确定FROM子句中ON后面的连接条件。
步骤3:分析查询是针对所有行,还是选择部分行。如果是选择部分行,使用WHERE子句,确定WHERE子句中的行条件表达式。
步骤4:分析查询是否要进行分组统计计算。如果需要分组统计,则使用GROUP BY子句,确定分组的列名。然后分析分组后是否要对组进行筛选,如果需要,则使用HAVING子句,确定组筛选条件。
步骤5:确定查询目标列表达式,即确定查询结果包含的列名或列表达式,确定SELECT子句后的目标列表达式。
步骤6:分析是否要对查询结果进行排序,如果需要排序,则使用ORDER BY子句,确定排序的列名和排序方式。