一、引题
学习思路:除了基本的语法外,一定要注意查询的逻辑,从需求去理解和记忆。
二、子查询
子查询特点:主查询的查询条件是另一个子查询的结果。
等价说法:一个子查询的查询结果,去作为主查询的查询条件。
补充:子查询的查询结果一般是一张表(部分字段构成)或者是一个值。
注意事项:
a.注意子查询要用括号括起来
b.不可以在group by 后面使用子查询
c.可以在主查询的where,select, having, from 后面使用子查询(位置)
d.子查询和主查询可以查不同的表,只要子查询查出的结果,主查询可以用就行
e.一般我们不在子查询中排序,但特殊情况下我们会在子查询中进行排序
f.一般先执行子查询,再执行主查询,但相关子查询例外
三、练习
需求1:查询工资比soctt这个员工高的员工信息(谁比我工资高)
分步查询的逻辑思路:
第一步:先查出scott员工的工资
第二步:再查出比scott工资大的员工信息
SELECT sal FROM emp WHERE ename='SCOTT'; -- 3000.00 SELECT * FROM emp WHERE sal>3000; -- 薪水值是主查询和子查询的纽带
子查询(综合):子查询的结果是一个值
SELECT * FROM emp WHERE sal>(SELECT sal FROM emp WHERE ename='SCOTT');
遇到的问题:表名无效
原因:数据库表名是否出现了小写字母或者关键字,以及是否书写了表名
注意:SCOTT大写(这个字段有大小写之分),另外()不要忘了
需求2:查询员工的部分信息,只要姓名和月薪
SELECT ename,sal FROM emp;
说明:查询的字段构成一张临时的表
子查询:理解子查询
SELECT * FROM (SELECT ename,sal FROM emp);
需求3:查询员工的信息,要姓名、工资、年薪、要求用子查询做
SELECT ename,sal,sal*12 AS "年薪" FROM emp;
说明:字段在查询的过程中可以参与运算。
需求4:查询部门名称是SALES(销售部)的员工信息
非子查询的思路1:通过deptno将两张表关联起来,查询部门名称是SALES(销售部)的编号的员工信息(多表查询)
SELECT *FROM emp,dept WHERE emp.deptno=dept.deptno AND dept.dname='SALES';
说明:获取到了两张表的所有字段信息(我们可以根据需要来挑选相应的字段),如下:
SELECT emp.ename,emp.job,emp.sal,emp.deptno FROM emp,dept WHERE emp.deptno=dept.deptno AND dept.dname='SALES';
非子查询的思路2:
第一步:我们先查部门是SALES的部门编号
第二步:查员工信息 条件就是第一步查出的结果
SELECT deptno FROM dept WHERE dname='SALES'; SELECT * FROM emp WHERE deptno=30;
子查询的思路3:
SELECT *FROM emp WHERE deptno=(SELECT deptno FROM dept WHERE dname='SALES');
注意:在查询的过程中只要用到表必须(from包含此表),即使不显示字段信息。
说明:主查询和子查询可以查询不同的表,主查询仅仅是用到了子查询的值而已
对比:以上的问题用子查询可以做,用多表查询也可以做,一般来说多表查询的效率要优于子查询。
-------------------------------------------------------------
需求5:查询部门名称是SALES(销售部)和ACCOUNTING(会计部)的员工
知识点1:in 在集合中,表示集合中的数据匹配上即可显示对应的字段信息
一般做法(or的逻辑运算符):
第一步:查询部门名称是SALES(销售部)和ACCOUNTING(会计部)的编号
第二步:根据编号去查询对应的员工信息。
elect deptno from dept where dname='SALES'or dname='ACCOUNTING';-- 10、30 select * from emp where deptno in(10,30);
子查询:
SELECT *FROM emp WHERE deptno IN(SELECT deptno FROM dept WHERE dname IN('SALES','ACCOUNTING'));
需求6:查询工资比30号部门任意一个员工高的员工信息
单步查询的步骤:
(1)查询出30号部门中的最高工资
(2)查询出员工信息,条件是 工资大于 30号部门的最高工资
select max(sal) from emp where deptno=30;-- 2850 select * from emp where sal>2850;
方法1:一般子查询
思路:查询30号部门的最高工资,然后进行比较筛选
SELECT* FROM emp WHERE sal>(SELECT MAX(sal) FROM emp WHERE deptno=30);
方法2:all子查询
知识点2:all,和集合中所有的值进行比较()
SELECT *FROM emp WHERE sal>ALL(SELECT sal FROM emp WHERE emp.deptno=30 );
说明:两种方式的查询结果一样,但是排列的顺序不一样,思考:查询逻辑?
需求7:查询工资比30号部门任意一个员工高的其他部门的(自己加的)员工信息
单步查询的步骤(不自己加的情况):
(1)查询出30号部门中的最低工资
(2)查询出员工信息,条件是工资大于30号部门的最低工资
select MIN(sal) from emp where deptno=30;-- 2850 select * from emp where sal>950;
方法1:一般子查询
SELECT * FROM emp WHERE sal>(SELECT MIN(sal) FROM emp WHERE deptno=30);
方法2:any子查询
知识点3:any,和集合中任意一个值比较
SELECT * FROM emp WHERE sal>ANY(SELECT sal FROM emp WHERE deptno=30);
补充:如果变成其他部门的员工,很显然要分组
思路1:
SELECT * FROM emp WHERE deptno NOT IN(30) AND sal>ANY(SELECT sal FROM emp WHERE deptno=30);
说明:NOT IN(30)可以换成<> 30或者!=30。
思路2:先条件然后分组最后再过滤
SELECT emp.ename,emp.sal,emp.deptno FROM emp WHERE sal>ANY(SELECT sal FROM emp WHERE deptno=30) GROUP BY deptno,sal,ename HAVING deptno NOT IN(30) ;
问题:不是GROUP BY 表达式
原因:在select需要查询的语句中选中的字段,必须出现在group by子句中,即使没用到它来分组(语法规定)。
相关链接:点击打开链接
几个概念总结下:
单行子查询---子查询的查询结果只有一条记录;多行自查询:子查询的查询结果有多条记录
多行子查询常用的操作符有如下: in、any、all
-----------------------------------------------
需求8:查询不是管理者的员工(一线员工或者说是底层员工)
说明:emp表中有两个字段empno和mgr分别表示员工编号和对应的管理者(上司)
正常思路:
SELECT * FROM emp WHERE empno NOT IN(SELECT mgr FROM emp );
结果:没有查出来,但是也没有报错
知识点4(原因):not in 中有null值
原因:not in 中如果有null值,就查不出来,但是不报错
具体:用的not in() 里面有空值,有一个员工没有mgr编号(没有上司)
解决思路:子查询中排除null值即可
SELECT * FROM emp WHERE empno NOT IN(SELECT mgr FROM emp WHERE mgr IS NOT NULL );
知识点5:in中有null值的问题
说明:in中是可以包含null 的,只不过不匹配null,并且不报错(null值没有查询到对应的字段)
SELECT * FROM emp WHERE mgr IN (7698,NULL);
需求9:查询是管理者的员工
说明:也就是如果员工编号在管理者编号中,那就是管理者,而此员工对应的管理者编号可以为null
SELECT * FROM emp WHERE empno IN(SELECT mgr FROM emp);
补充1:empno表示员工编号,mgr表示管理者编号。
补充2:empno和emp的字段值和类型必须要匹配才能进行。
-----------------------------------------------
需求10:查询员工表中工资最高的前三名(排序)
知识点6:伪列之一---rownum 行号
rownum:只是一个虚拟的字段
说明事项:
a. rownum 永远按照默认的顺序生成(但是一旦排序的话顺序会被打乱)
b. rownum 只能使用< 、<= 不能使用 > 、>= 因为Oracle的行号默认是从1生成的,有了1才能有2。
对默认排序的说明:
SELECT ROWNUM,emp.* FROM emp;
非默认排序的说明(按照sal进行排序,此时默认排序被打乱)
思路:由于经过排序后默认顺序已经打乱,此时如果再
SELECT ROWNUM,emp.* FROM emp WHERE ROWNUM<=3 ORDER BY sal ;
结果:
分析:未查询到结果,因为此时仍然是按照排序后会去匹配行号<=3。
正确思路:
第一步:我们查询出工资从大到小的排列结果
第二步:我们可以将第一步查询出的结果作为一张临时表,采用子查询的方式进行查询, 而这时主查询中的这个rownum 等于是给这个临时表又从1 开始编行号了。
select rownum,rn,ename,sal from (select ROWNUM AS rn,ename,sal from emp order by sal desc) where rownum<=3;
结果:
注意:此行号的伪列字段在排序前插入。
--------------------------------------------------
需求11:想要第二页的数据每页展示4条(分页查询)-- 多层嵌套
回顾:mysql的分页查询limit关键字
错误做法:
select rownum,ename,sal from emp where rownum<=8 and rownum>=5; 因为rownum不能使用 >= 号
分析:伪列(行号)rownum不能进行>、>=的比较
正确思路:插入伪列(行号A)后,把此表当作一张临时表,然后再对此临时表再插入一个伪列(行号B)得到一张新的临时表,此时
行号A的字段已经不是虚拟的字段(伪列),而是普通的字段,当然可以进行>、>=的比较了
即:首先我们从小到大查询出8条记录 ,然后将查出的这张表,作为一张临时表,继续再查
正确做法:
第一步:排序后的表显示前8条数据
select rownum r,ename,sal from (select rownum,ename,sal from emp order by sal desc) where rownum<=8
第二步:
select * from (select rownum r,ename,sal from (select rownum,ename,sal from emp order by sal desc) where rownum<=8) where r>=5;
与上做对比(上面的过于复杂,但是效率高)
select * from (select rownum r,ename,sal from (select rownum,ename,sal from emp order by sal asc) )WHERE r BETWEEN 5 AND 8;
注意:这里给rownum字段起了个名字r,是对伪列(行号--虚拟字段)起了个名字,不是查询的内层的rownum。
分页查询的相关链接:点击打开链接
-----------------------------------------------
需求12:查询员工表中工资大于本部门平均工资的员工
思路1:
第一步:查询各部门的平均工资
SELECT deptno, AVG(sal) FROM emp GROUP BY deptno;
第二部:查出工资大于平均工资的员工,条件是员工表的部门号等于临时表中的部门号,并且员工表中的工资大于临时表中的平均工资
select e.deptno,e.ename,e.sal,lsb.pj from emp e, (select deptno,avg(sal) as pj from emp group by deptno) lsb where e.deptno=lsb.deptno and e.sal>lsb.pj;
结果:各个部门员工的工资大于自己部门的平均工资筛选出来了。
注意:给字段起别名加不加as都可以,给表起别名后面尽量不能加as(以示区分),双引号括起来的表示特殊名字。
思路2:
知识点7:相关子查询
概念:将主查询中的值作为参数传递给子查询
思路:第一步先查出部门工资,给主查询的表起个别名,然后作为参数传给子查询
select deptno,ename,sal,from emp e where 条件是 工资>本部门的平均工资 (select avg(sal) from emp where deptno=e.deptno 条件是 子查询的部门号和主查询的部门号一样,才是本部门员工 所以我们给主查询的表起个别名,传给子查询来用
SQL语句
select e.deptno,e.ename,e.sal from emp e where e.sal>(select avg(sal) from emp where deptno=e.deptno);
补充:如果你想加一个平均工资字段,可以把整个子查询语句作为一个平均工资的字段,然后起个别名
select e.deptno,e.ename,e.sal,(select avg(sal) from emp where deptno=e.deptno) as "平均工资" from emp e where e.sal>(select avg(sal) from emp where deptno=e.deptno);
注意:字段别名如果是中文,最好用双引号括起来(不写也可以,但不能写单引号)
-------------------------------------------
知识点7:集合运算,就是对两个查询结果集,取并集、交集、差集(补集)
需求13:查询 10号 和 20号 部门的员工信息
方式1: 用in
select * from emp where deptno in(10,20);
方式2: 用or
select * from emp where deptno=10 or deptno=20;
方式3: 我们可以先查一个 10 号部门的员工得到一个结果集,再查一个 20号部门的员工得到一个结果集,将这两个结果集归并到一块 就是我们要的结果。
select deptno,ename from emp where deptno=10 union select deptno,ename from emp where deptno= 20;
说明:union是求取两个结果集的并集,如果两个结果集有交集元素,交集元素只要一份(没有重复)。
取并集还有一个运算符叫unionall,那union和unionall 有什么区别?
union --取两个结果集的并集,如果两个结果集有交集元素,交集元素只要一份
例如:A集合元素(1,2,3) union B集合元素(1,2,4) 结果是(1,2,3,4);
unionall 取两个结果集的并集,如果两个结果集有交集元素,交集元素各要一份
例如:A集合元素(1,2,3) union B集合元素(1,2,4) 结果是(1,2,3,1,2,4);
补充:如果两个集合没有交集,那union和unionall 取并集的结果是一样的。
后续补充交集和补集。。。。。。
------------------------
需求14:按照部门统计各部门不同工种的工资情况按照下图的格式输出
回顾:增强group by语句
(1)求各部门每个工种的总工资
(2)求每个所有部门的总工资
(3)求总工资
select deptno,sum(sal) from emp group by deptno; select deptno,job,sum(sal) from emp group by deptno,job; select sum(sal) from emp;
把以上三步结合在一起,用rollup()[group by的增强] 函数
select deptno,job,sum(sal) from emp group by rollup(deptno,job);
相关链接:
新的想法:采用交集的思路,我们把以上三步和在一块,就可以得到我们想要的结果 我们就可以用 union 把三步合并到一块
select deptno,job,sum(sal) from emp group by deptno,job union select deptno,sum(sal) from emp group by deptno union select sum(sal) from emp;
报错:查询快具有不正确的结果列数
分析:执行后语句虽然报错, 但是我们的思想是没错的, 只是语法上有错,那我们看一下取并集对每个集合是有些注意事项:
1. 参与运算的各个集合必须列数相同且数据类型一致;
2. 采用第一个集合作为最后的表头;
3. 如果有order by语句那么order by永远在最后一个集合后面;
4. 用括号可以改变语句的执行顺序
对症下药:如何保证每个集合的字段个数相同,和数据类型一致
select deptno,job,sum(sal) from emp group by deptno,job union select deptno,to_char(null),sum(sal) from emp group by deptno union select to_number(null),to_char(null),sum(sal) from emp; -- 只有一个字段,再给加两个空字段,类型和第一个集合的类型保持一致
说明:第一个集合是三个字段,第二个集合是一个字段(原),第三个集合是一个字段(原)
具体:to_number(null)和to_char(null)来保证
思考:集合和group by增强的这两种方式,哪个执行效率高
做法:我们可以打开Oracle统计时间的一个开关,那么在每次执行完语句后,就可以显示这条语句的执行耗时
set timing on ;--打开SQL语句执行时间的开关 -- sql语句 set timing off; --关闭说明: 集合运算是比较耗时的
--------------------------------------------------
练习说明:所有的练习均在scott用户下的四张表下进行(对于各表的含义以及结构可以自行熟悉)