聚合查询+联合查询+子查询

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 字段 from1 别名1 [inner] join2 别名2 on 连接条件 and 其他条件;
select 字段 from1 别名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 相比,主要的优势就是可以跨多个表进行数据合并


总结

  1. 在写复杂SQL的时候尽量不要一部到位,最好拆拆成多条SQL
  2. 实际开发尽量少用子查询,套娃太多可读性会比较差
  3. 多表查询效率较低,如果表的数量过多效率是非常低的。

以上是关于聚合查询+联合查询+子查询的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 数据库聚合查询和联合查询操作

MySQL 数据库聚合查询和联合查询操作

高级SQL查询-(聚合查询,分组查询,联合查询)

MySQL表的高级增删改查

『 MySQL篇 』:MySQL表的聚合与联合查询

MySQL的查询,子查询,联结查询,联合查询