🎇个人主页:Ice_Sugar_7
🎇所属专栏:数据库
🎇欢迎点赞收藏加关注哦!
前面我们讲查询时带表达式,比如成绩表中有语文、数学、英语三个科目,我们要查询总分,就要用到:
select chinese + math + english from...
这种查询方式实际上是在进行列和列之间的运算
而聚合查询,则是进行“行与行”之间的运算,不过这里行之间的运算有一定的限制——只能通过聚合函数来进行操作,这就需要用到SQL提供的一些库函数了。聚合查询不像表达式查询那样随便写表达式就可以了
下面我们来看一下常用的聚合函数:
现在有一个学生表:
select count(*) from students;
这行代码得到结果为4,它就相当于先执行:
select * from students;
然后再使用 count 来计算结果的行数
大多数情况下,count(*)和count(列名)没什么区别,在特定情况下才会有差异
比如,往上面的学生表中插入一个姓名为NULL的记录:
我们用 select count(*) 和 select count(name) 分别查询,得到的结果不一样
由此我们可以得出一个结论:查询指定列时,如果查询结果带有 null值,那么此时 null值的记录不会计入计数;而如果使用 count(*),则会计入计数
现在有如下的成绩表:
通过sum可以得到总的语文成绩
select sum(chinese) from grade;
sum 可以把某一列的值全部放在一起相加,在这个过程中,会把 null 给忽略掉,也就是说此时 null 相当于 0
除了数字求和,如果将名字进行求和会得到什么?
select sum(name) from grade;
可以看到只报警告而没有报错
在SQL中,如果把字符串当作数字来进行算术运算,就会尝试把字符串转成数字。但是上面这些名字是无法转成数字的,所以报警告
而如果我们插入一个数字字符作为名字的记录,那就可以转成数字了(不过现实中肯定没人这么起名的hhh)
这次就可以得到结果了:
至此,我们已经介绍了 sum 的用法,剩下的 avg、min、max 的用法和 sum 基本类似,所以也就不再赘述了,接下来就直接用了
现在我们要查询成绩表中,数学分数最低的同学:
但是我们查询出来的姓名和成绩对不上!
正常来说,一行数据每个列都是对应的(比如张一学号为1,数学考了80),这些列共同构成了一条记录。但是,如果查询中包含聚合函数和非聚合的列,那么结果中的列就不是对应的(相当于各打各的)
这是在因为使用聚合函数的时候,列和列之间的顺序已经被“打散”了。所以大部分情况下,聚合的列和非聚合的列不能一起使用。当然,有一种情况例外,那就是 group by
在此之前,先回到上面的例子,要找出数学成绩最低的同学,我们可以将 order by 和 limit 配合使用:
(顺便提一句,order by 默认是按升序排序的)
刚才的聚合是把整个表所有行都聚合在一起,不过也可以先把所有行分成若干组,然后分别对每个组进行聚合,需要用到 group by
group by 列名
它的效果就是把指定的列,其中值相同的记录划分到一组,针对这些组就可以分别进行聚合查询了
举个例子,比如现在要按照岗位,对职工表中员工的工资进行查询以及统计,就可以使用group by 了
在 select 列名的时候,列名是可以写role的,因为我们是按 role 来分组的
但是注意不能写 id 或者 name,因为这样写的话你会发现一个记录它的列是对不上的,如下图:
这就类似刚才上面查询数学成绩的同学这个例子
总结一下就是:非聚合的列不能和聚合的列或者 group by 的列一起使用
对于分组查询,它也是可以对查询结果进行条件筛选的,分组后的条件,用 having 来表示,它的用法和where是一样的,只不过 where 是用于分组之前的条件
下面应用一下 having
例1:要统计平均薪资高于10000的岗位
select role,avg(salary) from emp group by role having avg(salary) > 10000;
例2:要统计每个岗位的平均薪资,不包括李四,同时除去平均薪资低于13000的情况
统计平均薪资的时候不包括李四,这是在分组之前进行的,要用where;而筛选出平均薪资高于13000的情况,这是在分组后进行的,要用 having
select role,avg(salary) from emp where name != '李四' group by role having avg(salary) > 13000;
前面的查询都是针对一张表的,而多表查询自然是针对多张表的,它比单表查询要复杂一些
在正式讲多表查询之前,需要先引入一个概念——笛卡尔积
所谓笛卡尔积,其实就是简单的排列组合
笛卡尔积的列数就是之前两个表的列数之和;而行数则是之前两个表的行数之积
如果表更多,比如有三张表A、B、C,那么就是先计算 A 和 B 的笛卡尔积,算出来的结果再和 C 计算笛卡尔积
(注意:将两个很大的表进行笛卡尔积是一个危险操作,因为会产生大量的运算和 IO,可能把数据库搞挂了)
联合查询的核心操作就是进行笛卡尔积,比如使用两个表进行联合查询,就是先把这两个表计算笛卡尔积
比如同时查询学生表和成绩表:
select * from students,grade;
得到下面的结果:
细看一下我们会发现,有一些记录是无效的,就学生表中的 id 对不上 成绩表中的 id
所以我们再指定一些条件,来得到预期的查询结果
我们要让两个表中 id 一样的记录才能配对
select * from students,grade where students.id = grade.id;
students.id = grade.id 这个把两个表连接起来的条件,就叫作连接条件
不过也不是随便拿来两个表就能进行笛卡尔积,一定要确保这两个表有一定的关联关系,即至少有一个列是有关联的
总结:多表查询的一般步骤
①笛卡尔积
②连接条件
③根据需求指定其他条件
④针对列进行精简 or 使用聚合函数
除了上面的写法之外,联合查询还有一种写法,也能实现一样的效果:
select * from 表1 join 表2 on 连接条件;
所以上面的例子,我们也可以写为
select * from students join grade on students.id = grade.id;
可以得到一样的结果
再来看一个例子
现有学生表和课程表这两个表
现在要求每位同学所有课程的总分
思路:按照上面的步骤,先笛卡尔积,然后指定连接条件
我们要查看的列就是姓名和总分这两列
select students.name,sum(course.score) from students,course where students.id = course.student_id;
然后根据需求指定其他条件:因为我们是要统计每一位学生的成绩,所以就要按学生名字进行分组,这就要用到 group by
select students.name,sum(course.score) from students,course where students.id = course.student_id group by students.name;
接下来就是对列进行精简或者使用聚合函数,因为我们的目的就是求和,所以一开始就已经使用聚合函数 sum 了,这一步就不用再考虑了
上面举的例子的中有学生表和课程表这两个表,现在要再加入另一个表——课程学分表,它包括课程名字和课程学分
那如何把它和前面两个表连接起来呢?
我们可以发现,课程学分表和学生表之间其实没有联系,因为它们没有相关联的列;反之,它和课程表就很有联系——都有“课程名字”这一列
所以先把它们俩连起来
select * from course,credit where course.course_name = credit.course_name;
接下来再把这个表和学生表连起来
只需在原来的基础上,再查询一下学生表,并加上连接条件(即学生表的 id 和课程表的学生 id 相等),现在就有两个连接条件了,用 and 把两条件结合起来
select * from students,course,credit where students.id = course.student_id and course.course_name = credit.course _name;
不过查询结果的列数太多了,我们需要精简一下,三个表分别查询指定列就 ok 了
select students.name,students.id,course.course_name,course.score,credit.credit from students,course,credit where students.id = course.student_id and course.course_name = credit.course_name;
这条SQL语句看起来很长,但是我们拆分成一步步来写,其实也就没那么复杂了
上面的SQL语句也可以使用 join on 来改写:
select students.name,students.id,course.course_name,course.score,credit.credit from students join course on stude nts.id = course.student_id join credit on course.course_name = credit.course_name;
另外,在多表查询中,前面涉及到的去重、排序、limit 等操作也是同样适用的,具体就不演示了
刚才上面那些写法,叫作内连接,而外连接也是多表查询的一种体现形式
在大多数情况下,内连接和外连接的查询结果没什么区别,只有在一些特殊情况下,查询结果才会存在差异
那么“大多数情况”是什么样的情况呢?举个例子:
在上面的学生表和成绩表中,这两个表的数据是一一对应的,即学生表中的任何一个记录都能在分数表中体现出来;反过来,分数表中的每个记录,也能在学生表中体现出来,那此时外连接和内连接的结果就是一样的
前面我们说可以使用 join on 进行联合查询,其实那里的 join 前面省略了 inner,它表示内连接,一般 inner 是不用写的
而外连接分为左外连接和右外连接,需要在 join 前面分别加上 left 或 right
举例,现有两张表
注意,课程表中没有赵六的信息
分别对学生表和课程表进行左、右外连接,得到如下结果:
可以发现左外连接的结果有赵六这条记录,右表中的列全为 NULL;而右外连接没有这条记录
结论如下:
①左外连接就是以左表为主,保证左侧的表(join 左边的表)每个记录都体现在最终结果里,如果这个记录在右表中没有匹配,就把对应的列填成 NULL
②同理,右外连接就是以右表为主,保证右表中每个记录都存在,如果对应的数据在左表中没有,则会填成 NULL
其实就是套娃,把多个简单 sql 合并为一个复杂的 sql
注意:实际开发中不建议这样写,因为当有多个 sql 语句时,合并后就是一坨…
我们不写 ≠ 别人不写,为了看懂别人写的子查询 sql,我们还是有必要学习一下
举一个简单的例子,比如现在要从下面这个表中找出张一的同班同学的名字
照正常的思路,那就是先找出张一的班级:
然后再找出1班中名字不是张一的同学:
可以看到,在这个过程中,我们进行两次查询,先得到班级信息,然后由此进行第二次查询得到同学姓名
使用子查询的话,那就是将上面这两步合并起来:
select name from students where name != '张一' and classid = (select classid from students where name = '张一');
这个查询很好理解,就是把两个查询结果的结果集,合并成一个集合,这个过程我们使用 union 关键字来完成
语法:
select 1 union select 2
注意:合并查询要求两个 select 查询的结果集的列数和类型要匹配,最终的列名就是第一个 select 的列名
合并查询看起来和 or 差不多,但是 or 只能在一个表中进行查询,而 union 左右两侧的 sql 可以是查询两个不同的表,并且还会自动对查询结果进行去重(如果使用 union all 的话,则不会去重)
下面还是举些例子运用 union:
select * from students where name = '张三' union select * from students where id = 1;
select * from students where name = '张三' union select * from students where id = 2;