Oracle(二)

一、引题

学习思路:除了基本的语法外,一定要注意查询的逻辑,从需求去理解和记忆。

二、子查询

子查询特点:主查询的查询条件是另一个子查询的结果。

等价说法:一个子查询的查询结果,去作为主查询的查询条件。

补充:子查询的查询结果一般是一张表(部分字段构成)或者是一个值。

注意事项

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(会计部)的员工

知识点1in 在集合中,表示集合中的数据匹配上即可显示对应的字段信息

一般做法(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表中有两个字段empnomgr分别表示员工编号和对应的管理者(上司)

正常思路

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用户下的四张表下进行(对于各表的含义以及结构可以自行熟悉)


猜你喜欢

转载自blog.csdn.net/wzj_110/article/details/80500094