接进阶篇①,我们继续学习。
- 一、联合查询
- 1.1 内连接
- 1.2 外连接
- 1.3 内连接和左右外连接的区别
- 二、自连接
- 三、子查询
- 3.1 单行子查询
- 3.2 多行子查询
- 使用 in 范围匹配多行
- 另一种写法 exists
- 两种写法的区别
- 3.3 在 from 子句中使用子查询
- 四、合并查询
- union
- union all
一、联合查询
实际开发中往往数据来自不同的表,所以需要多表联合查询。
多表查询是对多张表的数据取笛卡尔积。
我们先初始化测试数据:
insert into classes(name, `desc`) values
('计算机系2019级1班', '学习了计算机原理、C和Java语言、数据结构和算法'),
('中文系2019级3班','学习了中国传统文学'),
('自动化2019级5班','学习了机械自动化');
insert into student(sn, name, qq_mail, classes_id) values
('09982','黑旋风李逵','xuanfeng@qq.com',1),
('00835','菩提老祖',null,1),
('00391','白素贞',null,1),
('00031','许仙','xuxian@qq.com',1),
('00054','不想毕业',null,1),
('51234','好好说话','say@qq.com',2),
('83223','tellme',null,2),
('09527','老外学中文','foreigner@qq.com',2);
insert into course(name) values
('Java'),('中国传统文化'),('计算机原理'),('语文'),('高阶数学'),('英文');
insert into score(score, student_id, course_id) values
-- 黑旋风李逵
(70.5, 1, 1),(98.5, 1, 3),(33, 1, 5),(98, 1, 6),
-- 菩提老祖
(60, 2, 1),(59.5, 2, 5),
-- 白素贞
(33, 3, 1),(68, 3, 3),(99, 3, 5),
-- 许仙
(67, 4, 1),(23, 4, 3),(56, 4, 5),(72, 4, 6),
-- 不想毕业
(81, 5, 1),(37, 5, 5),
-- 好好说话
(56, 6, 2),(43, 6, 4),(79, 6, 6),
-- tellme
(80, 7, 2),(92, 7, 6);
1.1 内连接
语法:
select 字段 from 表1 别名1 [inner] join 表2 别名2 on 连接条件 and 其它条件;
select 字段 from 表1 别名1,表2 别名2 where 连接条件 and 其它条件;
案例一: 查询 “许仙” 同学的成绩.
-- 写法一
select student.name,score.score from student,score where student.id=score.student_id and student.name='许仙';
--写法二
select student.name,score.score from student inner join score on student.id=score.student_id and student.name='许仙';
运行结果:
解析:
首先,我们需要判断出会涉及到哪些表,学生表和成绩表;
其次,将两个表先进行笛卡尔积操作(会出现很多数据);
select * from student,score;
接着,给 sql 语句添加连接条件,将无意义的数据过滤掉;
select * from student,score where student.id=score.student_id;
然后,逐渐添加题目中的其它条件(学生名字),一步步缩小数据范围;
select * from student,score where student.id=score.student_id and student.name='许仙';
最后再精简查询的列,就是最后的答案。
select student.name,score.score from student,score where student.id=score.student_id and student.name='许仙';
案例二: 查询所有同学的总成绩,以及同学的个人信息
-- 成绩表对学生表是 n 对 1 关系,查询总成绩需要根据成绩表的同学 id 来进行分组的
select stu.sn,stu.name,stu.qq_mail,sum(sco.score) from student stu,score sco where stu.id=sco.student_id group by sco.student_id;
执行结果:
案例三: 查询所有同学的成绩,以及同学的个人信息。
-- 学生表、成绩表、课程表 3 张表关联查询
select stu.sn,stu.name,stu.qq_mail,sco.score,sco.course_id,cou.name
from student stu,score sco,course cou
where stu.id=sco.student_id and cou.id=sco.course_id;
运行结果:
1.2 外连接
外连接分为左外连接和右外连接。
如果联合查询,左侧的表完全显示我们称为左外连接;右侧的表完全显示我们称为右外连接。
语法:
-- 左外连接
select 字段名 from 表名1 left join 表名2 on 连接条件;
-- 右外连接
select 字段 from 表名1 right join 表名2 on 连接条件;
案例: 查询所有同学的成绩,以及同学的个人信息。如果该同学没有成绩,也需要显示。
select stu.id,stu.sn,stu.name,stu.qq_mail,sco.score,sco.course_id,cou.name
from student stu
left join score sco on stu.id=sco.student_id
left join course cou on sco.course_id=cou.id
order by stu.id;
运行结果:
1.3 内连接和左右外连接的区别
假设两个表的每一条数据都是一一对应的,此时外连接和内连接是等价的。
如果有时候可能存在一些数据,没有对应关系,内外连接就有差别了。
例如:
内连接: 两个表内连接,有个没有成绩的同学没有显示出来。
左外连接: 一共 21 条记录,“老外学中文” 同学可以显示出来。
右外连接: 一共 21 条记录,“老外学中文” 同学可以显示出来。
观察案例可以发现,内连接的结果只包含在两张表都有体现的数据;
左外连接,左侧的表的数据是全的;
右外连接,右侧的表的数据是全的。
二、自连接
自连接是指在同一张表连接自身进行查询。
案例: 查询所有 “计算机原理” 成绩比 “Java” 成绩高的成绩信息。
select s1.student_id, s1.score as java, s2.score as '计算机原理'
from score s1, score s2
where s1.student_id = s2.student_id
and s1.course_id=1
and s2.course_id=3
and s1.score<s2.score;
-- join on 语句的写法来进行自连接查询
select s1.student_id,
s1.score as java,
s2.score as '计算机原理'
from score s1 join score s2
on s1.student_id=s2.student_id
and s1.score < s2.score
and s1.course_id=1
and s2.course_id=3;
运行结果:
以上查询只显示了成绩信息,要显示学生以及成绩信息,并且在一条语句显示:
SELECT
stu.*,
s1.score Java,
s2.score 计算机原理
FROM
score s1
JOIN score s2 ON s1.student_id = s2.student_id
JOIN student stu ON s1.student_id = stu.id
JOIN course c1 ON s1.course_id = c1.id
JOIN course c2 ON s2.course_id = c2.id
AND s1.score < s2.score
AND c1.NAME = 'Java'
AND c2.NAME = '计算机原理';
运行结果:
三、子查询
子查询是指嵌入在其它 sql 语句中的 select 语句,也叫嵌套查询。
3.1 单行子查询
返回一行记录的子查询
查询与 “不想毕业” 同学的同班同学
select * from student where classes_id=(
select classes_id from student where name='不想毕业');
3.2 多行子查询
返回多行记录的子查询
使用 in 范围匹配多行
查询 “语文” 或者 “英文” 课程的成绩信息
select * from score where course_id in (
select id from course where name='语文' or name='英文');
执行结果:
另一种写法 exists
也可以完成多行子查询
select * from score
where exists(
select score,course_id from course
where(name='英文' or name='语文')
and course.id=score.course_id
);
执行过程:
先执行外层查询,就会得到很多行记录。
每获取到外层查询的一行,就都会执行一次子查询的 SQL。
接下来,如果子查询的结果集合为空,那么外层查询的这一行记录,就被忽略。
如果子查询的结果集合非空,那么外层查询的这一行记录就会被记录。
exists 效果就是检测,先获取数据,在把数据一条一条的执行子查询 SQL,筛选掉不符合子查询的数据。
两种写法的区别
第一种基于 in 的写法,速度快,但是如果子查询的结果集合很大,内存放不下,就凉了。
第二种,基于 exists 的写法,速度慢,但是和内存的关系不大,哪怕子查询很大,也能保证执行出结果。
如果子查询结果集合比较小,优先使用第一种写法。
如果子查询结果集合比较大,并且外层查询的结果数量比较少,优先考虑第二种写法。
3.3 在 from 子句中使用子查询
子查询语句出现在 from 子句中。
这里要用到数据查询的技巧,把一个子查询当成一个临时表来使用。
查询所有比 “中文系2019级3班” 平均分高的成绩信息:
select avg(sco.score) score
from score sco
join student stu on sco.student_id=stu.id
join classes cls on stu.classes_id=cls.id
where cls.name='中文系2019级3班';
查询成绩表中,比以上临时表平均分高的成绩:
select * from score sco,(
select avg(sco.score) score
from score sco
join student stu on sco.student_id=stu.id
join classes cls on stu.classes_id=cls.id
where cls.name='中文系2019级3班'
)tmp where sco.score > tmp.score;
四、合并查询
在实际应用中,为了合并多个 select 的执行结果,可以使用集合操作符 union,union all。
使用 union,union all 时,前后查询的结果集,字段需要保持一致。
union
该操作符用于取得两个结果集的并集。使用该操作符的时候,会自动去掉结果集中重复的行。
查询 id 小于 3,或者名字为 ‘英文’ 的课程
select * from course where id<3
union
select * from course where name='英文';
执行结果:
其实使用 or 来实现更加简单。
select * from course where id<3 or name='英文';
union all
该操作符用于取得两个结果集的并集。
当使用此操作符的时候,不会去掉结果集中的重复行。
查询 id 小于 3,或者名字为 ‘Java’ 的课程
select * from course where id<3 union all select * from course where name='Java';
运行结果:有重复行。