前面我们讲解的mysql表的查询都是对一张表进行查询,在实际开发中这远远不够。下面我们来学习一些MySQL中更复杂的查询。
下面的表还是来自oracle 9i的经典测试表。
查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J
可以看到内置函数在where子句中也是可以使用的。
select * from emp where (sal>500 or job='MANAGER') and substring(ename,1,1)='J';
按照部门号升序而雇员的工资降序排序
select ename, deptno, sal from emp order by deptno asc,sal desc;
使用年薪进行降序排序
我们看到在emp表中有的员工的comm奖金为NULL,而NULL不会参与运算,任何数和NULL计算都是NULL。所以我们计算年薪时,如果这个员工的comm奖金为NULL,那么这个员工的年薪就会为NULL。所以我们需要ifnull来做一个判断,如果这个员工的comm为NULL,那么就认为这个员工的comm为0;如果这个员工的comm不为NULL,那么就将这个员工的奖金加入到年薪中。这样就避免了NULL参与运算。
select ename,sal,comm,sal*12+ifnull(comm,0) 年薪 from emp order by 年薪 desc;
显示工资最高的员工的名字和工作岗位
我们看到where子句中可以再包含一个select子查询。这个语句是由内向外执行的,即先执行里面的子查询。
select ename,job from emp where sal=(select max(sal) from emp);
显示工资高于平均工资的员工信息
select * from emp where sal>(select avg(sal) from emp);
显示每个部门的平均工资和最高工资
select deptno, max(sal), format(avg(sal),2) from emp group by deptno;
显示平均工资低于2000的部门号和它的平均工资
下面的语句会先执行from emp分析出来检索哪个表,然后执行group by子句进行分组。然后根据deptno会形成3个表,每个表都执行select 子句后面的聚合函数。最后将三个表得到的结果都执行having子句进行判断,然后得到最终的结果。
select deptno, format(avg(sal),2) from emp group by deptno having avg(sal)<2000;
显示每种岗位的雇员总数,平均工资
下面语句的执行顺序为先执行from emp子句确定要检索的表,然后执行group by job子句通过job进行分组,形成若干个临时表,然后这若干个临时表都执行select 子句和聚合函数。最后每个临时表将自己的结果显示出来形成一个结果临时表。注意我们可以认为MySQL中每一次语句的执行形成的结果都是一个临时表,这样就更方便理解了。
select job,count(*) 人数, format(avg(sal,2) 平均工资 from emp group by job;
实际开发中往往数据来自不同的表,所以需要多表查询。下面我们继续使用来自oracle 9i的经典测试表。
显示雇员名、雇员工资以及所在部门的名字
因为上面的数据来自emp和dept表,因此要联合查询。
我们可以将emp表和dept表进行整合,整合后形成一个临时的表。
我们看到emp表和dept表整合形成的表是将emp的每一条数据都分别匹配dept表的每一条数据。即将两个表中的数据进行穷举组合,这就类似笛卡尔积。
将emp表和dept表进行笛卡尔积整合后形成的表中有很多无效数据,所以我们需要根据deptno来将表中有效数据筛选出来。
然后我们显示我们需要的列数据即可。
显示部门号为10的部门名,员工名和工资
这个题还是需要显示emp表和dept表两个表中的列数据。所以我们先将两个表进行整合。
然后筛选出来有效的数据,因为规定了只显示部门号为10的员工,所以我们多加一个筛选条件。
然后我们根据题意选择要显示的列信息。因为deptno在emp表和dept表中都有这一列,所以当我们要显示这一列信息时需要指明显示哪个表的这一列信息。
显示各个员工的姓名,工资,及工资级别
因为需要显示emp和salgrade两个表中的列数据,所以我们先将emp表和salgrade表进行整合。
然后我们选择要显示的列数据,并且筛选工资在losal和hisal之间的员工。
我们上面将两个表整合都是两个不同的表。自连接是指在同一张表连接查询,例如将emp表和emp表本身进行整合。那么自连接适用于什么场景呢?我们看下面的案例。
显示员工ford的上级领导的编号和姓名(mgr是员工领导的编号)
使用子查询办法:
这个题我们可以先使用子查询查询出来ford员工的上级领导编号,然后再根据where条件筛选出来领导的信息。
select ename,empno from emp where empno=(select mgr from emp where ename='FORD');
使用自连接多表查询:
我们还可以将emp表进行自连接。
我们需要注意当使用自连接时,因为两个表的名字相同,所以需要在from 子句中对表进行重命名,然后后面就可以使用这两个表的重命名来拿到列数据了。
我们再根据where条件筛选出来满足条件的数据,并且将这个数据按照题意显示列数据。
select e2.ename,e2.empno from emp e1,emp e2 where e1.ename='FORD' and e1.mgr=e2.empno;
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询。前面我们已经简单的使用了子查询,下面我们来更详细的学习子查询。
我们通过下面的案例可以知道,单行子查询其实就是子查询返回的结果只有一行数据,所以才叫做单行子查询。
显示SMITH同一部门的员工
我们可以先通过select查询到SMITH员工的部门号,然后再通过where条件筛选出部门号对应的员工。
返回多行记录的子查询。
in关键字:in用于判断某个记录的值是否在指定的集合中,在in关键字前边加上not可以将条件反过来。
查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含10自己的
我们先查询出来10号部门的工作岗位,然后将这个查询作为子查询。可以看到子查询的结果为多行,所以说是多行子查询。然后我们使用in关键字判断emp表中员工的job在子查询的结果中的员工,并且显示这些员工的信息。
all关键字:与子查询返回的所有值比较,如果比较的全部结果都满足则返回true。
显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
这个题我们可以使用单行子查询,即先算出30部门的最高工资。然后再拿这个子查询的结果进行where条件筛选。
我们也可以使用多行子查询的方法来写,即先查询出30部门的员工的所有工资,然后使用all关键字,即将emp表中的员工的工资和多行子查询的结果进行比较,如果员工的工资大于多行子查询的所有结果,那么就说明这个员工的工资高于30部门所有员工的工资。
any关键字:与子查询返回的所有值比较,如果比较的任意一个结果满足则返回true
显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门的员工)
这个题我们也可以使用子查询来解决,题目中说的是30部门任意员工的工资,所以我们可以先算出30部门的员工最低工资,然后只要emp表中的员工工资大于30部门的最低工资,那么就说明该员工工资比30部门任意员工的工资高。
我们也可以使用多行子查询来解决,我们先筛选出30部门的所有员工工资。然后我们使用any关键字进行比较,只要tmp表中的员工工资大于多行子查询结果的任意一个,即只要有一个,那么就说明这个员工工资大于30部门的任意一个员工的工资。
单行子查询是指子查询只返回单列,单行数据;多行子查询是指返回单列多行数据,都是针对单列而言的,而多列子查询则是指查询返回多个列数据的子查询语句。
查询和SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人
我们看到查询SMITH员工的部门和岗位会显示两列信息,所以这时候使用单列对比是不行的。此时就需要使用多列对比。多列对比中我们需要注意比较的两个列一定要对应。
我们也可以使用in进行判断。虽然in可以用于多列多行的判断。但是也可以使用in来判断多列单行,只不过是比较的只有一行而已。
子查询语句出现在from子句中。这里要用到数据查询的技巧,把一个子查询当做一个临时表使用。
显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资
我们先算出部门平均工资临时表,然后将员工表和部门平均工资临时表做笛卡尔积。需要注意的是当查询结果作为临时表在from后面时,必须要为这个临时表进行重命名,因为这样才能通过重命名在后序拿到这个临时表的列数据。
然后我们筛选出来有效数据,即将两个表中deptno相同并且工资大于部门平均工资的员工的数据筛选出来。
这个最终的结果也可以再作为一个表和新的表进行笛卡尔积。
例如我们再增加一个要求,得到这些员工的办公地点。此时我们就需要将这个结果临时表和dept表做笛卡尔积。
然后我们再筛选出有效数据并且选择最后要显示的列数据。
查找每个部门工资最高的人的姓名、工资、部门、最高工资
我们先找到部门最高工资临时表,然后将emp表和部门最高工资临时表进行笛卡尔积。
然后我们再筛选出来符合条件的有效数据并且选择要显示的列信息。
select emp.ename, emp.sal, emp.deptno, t1.ma from emp, (select deptno,max(sal) as ma from emp group by deptno) as t1 where emp.deptno=t1.deptno and emp.sal = t1.ma;
显示每个部门的信息(部门名,编号,地址)和人员数量
多表子查询:
我们先得到部门人数临时表,然后将dept表和部门人数临时表进行笛卡尔积。
然后再通过条件筛选出有效数据,并且选择最后要显示的列数据。
多表查询:
我们先直接将emp表和dept表进行笛卡尔积。
然后我们再筛选出来有效数据。
然后我们根据deptno进行分组,再使用聚合函数算出来每个组有多少员工。
然后我们再选择需要显示的列信息进行显示。这里我们需要注意,因为语句中使用了order by子句,所以select 子句后面只能显示聚合函数和order by子句分组用到的列信息。如果我们在最终结果中要显示dname和loc的话,那么我们需要将这两个列信息也加入到order by子句的分组列信息中。
在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all。我们需要注意必须两个select检索产生的临时表的列属性和列结果都相等,才能进行合并。
该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。
将工资大于2500或职位是MANAGER的人找出来
我们看到union在取并集时会自动进行去重。
如果我们不想要将并集的结果进行去重,那么就可以使用union all,该操作符用于取得两个结果集的并集。当使用该操作符时,不会去掉结果集中的重复行。
通过上面的练习,我们可以总结出,解决多表查询问题的本质就是想办法将多表转化成为单表,然后就转换为单表查询问题了。