聚合查询+联合查询+子查询
Posted 爱敲代码的三毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聚合查询+联合查询+子查询相关的知识,希望对你有一定的参考价值。
一、聚合查询
1.聚合函数
常见的统计总数,计算平均值的操作,可以用聚合函数来实现,常见的聚合函数有:
这些函数都比较简单,通过几条SQL语句就能理解
- count
-- 求表中一共有多少个学生
select count(*) from student;
-- 求共有多少学提供了 qq邮箱
select count(qq_mail) from student;
注意: count()之间不能有空格,且null不会记录到最终结果中
count 函数相当于是先执行select * ,再根据 select * 放回的结果统计数量
- sum
-- 统计数学成绩总分
SELECT SUM(math) FROM exam_result;
-- 不及格 < 60 的总分,没有结果,返回 NULL
SELECT SUM(math) FROM exam_result WHERE math < 60;
- avg
-- 统计平均总分
SELECT AVG(chinese + math + english) 平均总分 FROM exam_result;
- max
-- 返回英语最高分
SELECT MAX(english) FROM exam_result;
- min
-- 返回 > 70 分以上的数学最低分
SELECT MIN(math) FROM exam_result WHERE math > 70;
2.GROUP BY子句
SELECT 中使用 GROUP BY 子句可以对指定列进行分组查询。需要满足:使用 GROUP BY 进行分组查询时,SELECT 指定的字段必须是“分组依据字段”,其他字段若想出现在SELECT 中则必须包含在聚合函数中
-- 创建表
create table emp (
id int primary key auto_increment,
name varchar(20) not null,
role varchar(20) not null,
salary numeric(11,2)
);
-- 插入数据
insert into emp(name,role,salary) values
('孙悟空','员工',3300.5),
('猪八戒','员工',4000),
('沙僧','员工',3700.50),
('唐僧','主管',6000),
('白龙马','主管',5800),
('老王','老板',10000.88);
用 group by 对 role 职位分组,分别求每个职位的最高工资、最低工资何平均工资
select role,max(salary),avg(salary), avg(salary) from emp group by role;
3.HAVING
GROUP BY 子句进行分组以后,需要对分组结果再进行条件过滤时。
注意:where 的条件筛选是针对当前表中原始的数据(聚合前的数据)来进行筛选, 而现在咱们是期望针对聚合后的数据进行筛选
针对聚合后的数据进行筛选,要使用的是 having 子句,语法和 where 类似。
代码示例:
我们要查询一个平均薪资大于4000的岗位,以岗位分组。
select role, avg(salary) from emp
group by role having avg(salary) > 4000;
where 也能和 group by 搭配 只不过这样的搭配是根据需求来确定
如果需求中的条件,是针对聚合前的的数据进行筛选,就使用 where
如果需求中的条件,是针对聚合后的的数据进行筛选,就使用 having
代码示例:
我们要查询一个平均薪资大于4000的岗位且排除掉唐僧,使用 where条件过滤掉唐僧,唐僧就不会被分到任何组了
select role, avg (salary) from emp
where name != '唐僧' group by role having avg(salary) > 4000;
二、联合查询
实际开发中往往数据来自不同的表,所以需要多表联合查询。多表查询是对多张表的数据取笛卡尔积,也就是获取两个表的排列组合,把两张表中数据组合的所有可能都列举出来。但是有很多数据是没有意义的。
在 SQL中计算笛卡尔积(多表查询)主要有两种方式
1.直接 select from 表1,表2…
2.select from 表1 join 表2 on 条件… join 表3 on 条件
注意:在SQL中如果两个表的字段名冲突可以通过点来指定某张表的字段,类似于Java的成员访问操作符。
表名.字段名,
同样可以
数据库名.表名.字段名
代码示例:
创建一个班级表、学生表,课程表,和分数表。
drop table if exists classes;
drop table if exists student;
drop table if exists course;
drop table if exists score;
create table classes (id int primary key auto_increment, name varchar(20), `desc` varchar(100));
create table student (id int primary key auto_increment, sn varchar(20), name varchar(20), qq_mail varchar(20) ,
classes_id int);
create table course(id int primary key auto_increment, name varchar(20));
create table score(score decimal(3, 1), student_id int, course_id int);
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','曹操',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),
-- 曹操
(80, 7, 2),(92, 7, 6);
共创建了4张表
分数表就是课程表和学生表的中间表
再进行笛卡尔积(多表查询)
select * from classes,course,score,student;
得到了2000多行数据,但笛卡尔积是无脑排列组合这里大部分数据是没有意义的。
我们就要对数据进行筛选,筛选出 分数表中student_id和学生表中id相等的数据
select * from student, score where student.id = score.student_id;
假如我们要查询每个同学的分数
通过 学生id 和分数表中的学生id 再加上 课程id 和分数表的课程id 相等来筛选。
select student.name,course.name,score.score
from student, course,score
where student.id = score.student_id
and course.id = score.course_id;
join on
假设我们要查询许仙的成绩
select student.name,score.score,score.course_id
from student,score where student.id = score.student_id
and name = '许仙';
我们还可以通过join on达到同样的效果
select student.name,score.score,score.course_id
from student join score
on student.id = score.student_id and name = '许仙'
既然两种写法,没有本质的区别,为啥有两种写法呢?
只不过是在当前列子里没有区别而已
但是在别的场景中,join on 可能会和 from 多个表 where 的写法有本质的区别。
from 多个表 where 写法,仅仅是 内连接
join on 这样的写法,既可以表示 内连接,还可以表示 左外连接和右外连接
1.内连接
语法:
select 字段 from 表1 别名1 [inner] join 表2 别名2 on 连接条件 and 其他条件;
select 字段 from 表1 别名1,表2 别名2 where 连接条件 and 其他条件
我们创建学生表和分数表
create table student (
id int primary key auto_increment,
name varchar(20));
create table score (
studentId int,
score int);
insert into student values
(null,'张三'),
(null,'李四'),
(null,'王五');
insert into score values(1,90);
insert into score values(2,80);
insert into score values(4,70);
查询学生的成绩
select student.name,score.score
from student join score on student.id = score.studentId;
内连接其实就是取出两个表中数据的交集
2.外连接
外连接分为左外连接和右外连接。如果联合查询,左侧的表完全显示我们就说是左外连接;右侧的表完全显示我们就说是右外连接
语法:
-- 左外连接,表1完全显示
select 字段名 from 表名1 left join 表名2 on 连接条件;
-- 右外连接,表2完全显示
select 字段 from 表名1 right join 表名2 on 连接条件;
左外连接:最终查询结果,是join左侧的表为主,会尽可能的把左侧的表的所有信息都体现出来
select student.name,score.score
from student left join score on student.id = score.studentId;
右外连接:和左外连接类型,是以 join 侧的表为主,尽可能把右侧的每个信息都体现出来
select student.name,score.score
from student right join score on student.id = score.studentId;
全外连接:mysql是不支持全外连接的
3.自连接
自连接就是一张表,自己和自己做笛卡尔积。
SQL比较方便可以进行列和列的比较,但是不方便进行 行和行之间的比较。
就比如上面那个分数表的例子,不同科目的分数是以行的方式来展示的
显示所有“计算机原理”成绩比“Java”成绩高的成绩
针对这个行和行比较,咱们不好搞,就转换成列和列比较,就是使用自连接来进行转换。
在进行自连接的时候,需要先给表起别名,否则就会报错,as是可以省略
select * from score as s1,score as s2;
再加上 student_id 的条件,筛选数据,期望把同一个学生的信息分别放到左右两侧
select * from score as s1, score as s2
where s1.student_id = s2.student_id
and s1.course_id = 1 and s2.course_id = 3;
最后
select * from score as s1, score as s2 where s1.student_id = s2.student_id and s1.course_id = 1 and s2.course_id = 3
and s1.score < s2.score;
三、子查询
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询
- 单行子查询:先执行一个查询,这个查询返回一条记录,以这个返回结果作为另一个查询的条件,进行最终查询。
代码示例:查询 张飞的同班同学
如果不用子查询
select classes_id from student where name = '张飞';
select name from student where classes_id = 1;
如果采用子查询,只需要一条代码
select name,classes_id from student where
classes_id=(select classes_id from student where name = '张飞');
- 多行子查询:子查询返回的数据不只是一条了,会有多条记录
当我们要查询语文或者Java的课程信息,如果用多条SQL
select id from course where name = '语文' or name = 'Java';
select * from score where course_id = 1 or course_id = 4;
使用子查询也能达到同样的效果
select * from score where course_id in (
select id from course where name = '语文' or name = 'Java');
EXISTS关键字
使用 exists 关键字也可以进行子查询,也是对应子查询返回多条记录的情况
select * from score where exists (
select score.course_id from course where
(name = '语文' or name = 'Java') and
course.id = score.course_id);
也能达到同样的效果
exists 工作过程:
先执行外层查询,需要遍历表中的每个记录(遍历score表的每个记录)
拿着当前这条记录,执行子查询。
这个写法中,子查询执行的次数,和外层表的行数是一样多的(执行效率相当低,执行SQL的次数太多了)
而在前面的in写法中子查询只执行一次(执行效率高,但是占用更多内存,会把子查询的结果都放到内存中)
如果外层查询的表很大,里层查询的表很小,适合用in(里层表小,内存占用就少)
如果外层查询的表很小,里层查询的表很大,适合用 exists(里层查询得到的结果太多了,内存放不下,此时就不能用 in了)
当然内外层都很大的话就都别用了吧,老老实实拆成多条语句吧
四、联合查询
在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all。使用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';
union 和 or 相比,主要的优势就是可以跨多个表进行数据合并
总结
- 在写复杂SQL的时候尽量不要一部到位,最好拆拆成多条SQL
- 实际开发尽量少用子查询,套娃太多可读性会比较差
- 多表查询效率较低,如果表的数量过多效率是非常低的。
以上是关于聚合查询+联合查询+子查询的主要内容,如果未能解决你的问题,请参考以下文章