Oracle基本查询 二

Part III(面试题目) 
01. tmp 表中有如下记录(建表SQL 见emp.sql) 

要求结果格式为: 
答:考察知识点:case… when… count 函数 
select rq, 
count(case when shengfu='WIN' then 1 else null end) WIN, 
count(case when shengfu='LOSE' then 1 else null end) LOSE from tmp group by rq ; 
分析: 
要点:在计数时,配合case…when…语句只统计取值为WIN 或LOSE 的个数。case 语句返
回不为null 的值即会加入count 计数,返回null 则不会加入count 计数。 
02. 查询当前月有多少天 
答:考察知识点 日期函数的灵活运用 
select trunc(add_months(sysdate,1),'month') - trunc(sysdate,'month') from dual; 
03. pages 表有四个字段,id, url,title,body。如图: 
现要求将url 匹配的排在最前,title 匹配的其次,body 匹配最后,没有任何字段匹配的,不
返回。现要求查询所有匹配baidu 的记录,最终查询结果如图: 

建表语句在emp.sql 中 
答:考察知识点:union 
select id,content from ( 
select id, 3 mark, url content from pages where url like '%baidu%' 
union 
select id, 2, title from pages where title like '%baidu%' 
union 
select id, 1, body from pages where body like '%baidu%' 
) order by mark desc; 
要点:union 可以用来合并多次查询结果。这里需要注意多次查询的结果列的个数和类型必
须相同,合并后的结果集也可以看做一张表,表的列的类型和名称由union 的第一条查询结
果来决定。 
这里用到一个技巧:手工指定一个优先级mark 列,最后根据mark 列排序。 
04. 现有STUDENT(学生), COURSE(课程), SC(成绩)表,完成以下需求(建表语句在emp.sql 
中,综合考察) 
a) 查询选修课程为web 的学员学号和姓名 
答: 
select s.sid,s.name from student s 
inner join sc on(s.sid=sc.sid) inner join course c on (c.cid=sc.cid) 
where c.name = 'web'; 
分析:课程与学生表没有直接联系,必须通过中间成绩表做2 次表连接 
b) 查询课程编号为2 的学员姓名和单位 
答: 
select s.name,s.dept from student s inner join sc on (s.sid=sc.sid) 
where sc.cid = 2; 

c) 查询不选修4 号课程的学员姓名和单位 
答: 
select name,dept from student where sid not in 
(select s.sid from student s left join sc on s.sid = sc.sid where cid = 4); 
或: 
select name,dept from student 
where not exists(select sid from sc where sc.sid = s.sid and cid = 4); 
分析:要点是先要查询出选修了4 号课程的学员id,再从所有学员中排除这些id 的学
员。方法2 效率较高。 
d) 查询选修全部课程的学员姓名和单位 
答: 
select s.name,s.dept from student s where sid in 
(select sid from sc group by sid having (count(*) = (select count(*) from course))); 
分析: 
步骤1:查询出所有课程的数目 
select count(*) from course; 
步骤2:在成绩(sc)表,按学员id 分组,看每组的个数,该个数等于步骤1 课程总数的
sid 即为选修了所有课程的学员id 
select sid from sc group by sid having (count(*) = (select count(*) from course)); 
步骤3:再根据该sid 查询学员的详细信息 
select s.name,s.dept from student s where sid in 
(select sid from sc group by sid having (count(*) = (select count(*) from course))); 
e) 查询选修课程超过3 门的学员姓名和单位 
答: 
select s.name,s.dept from student s 

where sid in (select sid from sc group by sid having (count(*) > 3)); 
f) 找出没有选修过Teacher LI 讲授课程的所有学生姓名 
答: 
select s.name from student s where sid not in 
(select sid from course c left join sc on (c.cid = sc.cid) where c.teacher='Teacher LI'); 
g) 列出有二门以上(含两门)不及格课程的学生姓名及其平均成绩 
答: 
select s.*, b.avgsal from student s, 
(select sc.sid,avg(score) avgscore from sc , 
(select sid from sc where score < 60 group by sid having(count(*) >=2)) a 
where sc.sid = a.sid group by sc.sid) b 
where s.sid = b.sid; 
分析: 
步骤1:查询所有两门以上不及格的学员id 
select sid from sc where score < 60 group by sid having(count(*) >=2); 
步骤2:步骤1 结果与真实表sc 做连接,算平均成绩 
select sc.sid,avg(score) avgscore from sc, 
(select sid from sc where score < 60 group by sid having(count(*) >=2)) a 
where sc.sid = a.sid group by sc.sid; 
步骤3:步骤2 结果与真实表student 做连接,查学员姓名 
select s.*, b. avgscore from student s, 
(select sc.sid,avg(score) avgscore from sc , 
(select sid from sc where score < 60 group by sid having(count(*) >=2)) a 
where sc.sid = a.sid group by sc.sid) b 
where s.sid = b.sid; 
思路2: 
步骤1:同上 
步骤2:步骤1 结果与真实表sc,student 共3 张表做连接 
select s.*,avg(sc.score) avgscore from student s, sc, 

(select sid from sc where score < 60 group by sid having(count(*) >=2)) a 
where s.sid = sc.sid and s.sid = a.sid; 
步骤3:可以发现,该结果中sid, name, dept, age 都是取值都相同,按照这些列直接进
行分组即可: 
select s.*,avg(sc.score) avgscore from student s, sc, 
(select sid from sc where score < 60 group by sid having(count(*) >=2)) a 
where s.sid = sc.sid and s.sid = a.sid group by s.sid, s.name, s.dept, s.age; 
h) 列出既学过1 号课程,又学过2 号课程的所有学生姓名 
答: 
select s.name from student s inner join 
(select sc.sid from sc where sc.cid in (1,2) group by sid having (count(*) = 2)) a 
on (s.sid = a.sid); 
分析:要点是不仅要学过1,2 号课程in (1,2),并且要求同时学过此两门课count(*) = 2 
i) 列出1 号课成绩比2 号课成绩高的所有学生的学号,姓名和1 号课和2 号课的成
绩 
答: 
select s.sid, s.name, sc1.score, sc2.score from sc sc1,sc sc2,student s 
where s.sid = sc1.sid and sc1.sid = sc2.sid 
and sc1.cid = 1 and sc2.cid = 2 and sc1.score > sc2.score; 
分析:要点在于自连接,把成绩表拆成两张表来看,sc1 中只考虑1 号课,sc2 中只考
虑2 号课且sc1.score > sc2.score;最后再考虑将结果与student 表连接查询姓名。 
05. 现有test 表,表中数据如图所示: 

要求按照格式如下输出结果: 
a) 连续的编号要求如下格式 
b) 不连续的编号要求如下格式 
答: 
a) 求连续的,考察知识点:rownum,子查询 
分析:查看连续id 与rownum 之间的关系,运行 
select id, rownum, id-rownum from test; 
参考下图看出规律: 

可以发现,id-rownum 取值相同的,就是那些id 编号连续的。按照id-rownum 分组并求
每组的最大,最小值即可。 
select a.* from 
(select min(id) begin, max(id) end from test group by (id - rownum) order by id - rownum) a; 
b) 不连续的,考察知识点,rownum,子查询 
分析: 
步骤一: 
查询有上一条记录的个数 
select id, (select count(*) from test where id+1 = t1.id) from test t1; 
查询有下一条记录的个数 
select id, (select count(*) from test where id-1 = t1.id) from test t1; 
分析结果可知,个数为0 的即为我们所需要的。 
步骤二: 
select id, rownum r1 from test t1 where (select count(*) from test where id+1 = t1.id) = 0; 
select id, rownum r2 from test t1 where (select count(*) from test where id-1 = t1.id) = 0; 
分析结果可知,要求如上图格式的数据将查询1 中的r1-1 = 查询2 中的r2 列即可: 
select b.id begin, a.id end from 
(select id, rownum r1 from test t1 where (select count(*) from test where id+1 = t1.id) = 0) a, 
(select id, rownum r2 from test t1 where (select count(*) from test where id-1 = t1.id) = 0) b 

where r1-1=r2; 
06. 根据EMP 表数据产生如下格式的报表(统计各部门,各职位的人数) 
答:方法1 考察知识点case 
select deptno, 
count(case when job = 'PRESIDENT' then 1 else null end) PRESIDENT, 
count(case when job = 'MANAGER' then 1 else null end) MANAGER, 
count(case when job = 'CLERK' then 1 else null end) CLERK, 
count(case when job = 'SALESMAN' then 1 else null end) SALESMAN, 
count(case when job = 'ANALYST' then 1 else null end) ANALYST 
from emp group by deptno order by deptno; 
方法2 考察知识点:自连接 
select d.deptno, 
count(distinct PRESIDENT.empno) PRESIDENT, 
count(distinct MANAGER.empno) MANAGER, 
count(distinct CLERK.empno) CLERK, 
count(distinct SALESMAN.empno) SALESMAN, 
count(distinct ANALYST.empno) ANALYST from dept d 
left join emp PRESIDENT 
on (d.deptno=PRESIDENT.deptno and PRESIDENT.job='PRESIDENT') 
left join emp MANAGER 
on (d.deptno=MANAGER.deptno and MANAGER.job='MANAGER') 
left join emp CLERK 
on (d.deptno=CLERK.deptno and CLERK.job='CLERK') 
left join emp SALESMAN 
on (d.deptno=SALESMAN.deptno and SALESMAN.job='SALESMAN') 
left join emp ANALYST 
on (d.deptno=ANALYST.deptno and ANALYST.job='ANALYST') 
group by d.deptno order by d.deptno; 
分析:通过dept 表多次左外连接emp 表,比如说 
select d.deptno, d.dname, e.empno, e.job from dept d 
left join emp e on (d.deptno = e.deptno and e.job='CLERK'); 
结果如下: 

可以看出这是求出每个部门职位为CLERK 的员工,将此结果按deptno 分组求个数: 
select d.deptno, count(empno) CLERK from dept d 
left join emp e on (d.deptno = e.deptno and e.job='CLERK') 
group by d.deptno order by d.deptno; 
其中CLERK 列即为最终结果所需列。 
如此类推,连接一次,求出一列,但需要注意,多表连接后,最后结果中会有重复记录,因
此使用count(distinct empno)排除重复记录后再计算个数才为正确结果。 
07. 根据EMP 表数据产生如下格式的报表(统计各职位,各部门的人数)(06 题的变体) 
答:方法1 考察知识点case 
select job, 
count(case when deptno = 10 then 1 else null end) "10", 
count(case when deptno = 20 then 1 else null end) "20", 
count(case when deptno = 30 then 1 else null end) "30" 
from emp group by job order by job; 
思路:同第06 题,注意列别名如果为数字开头必须使用双引号。 
方法2 考察知识点:自连接 

select e.job, count(distinct d10.empno) "10",count(distinct d20.empno) "20", count(distinct 
d30.empno) "30" from 
(select job from emp group by job) e 
left join emp d10 on (e.job=d10.job and d10.deptno = 10) 
left join emp d20 on (e.job=d20.job and d20.deptno = 20) 
left join emp d30 on (e.job=d30.job and d30.deptno = 30) group by e.job order by job; 
08. 按照如下格式显示7369 号员工的信息 
答:考察知识点 UNION 
select empno, 'ENAME' as KEY, ename VALUE from emp where empno = 7369 
union 
select empno, 'JOB', job from emp where empno = 7369 
union 
select empno, 'HIREDATE', to_char(hiredate,'yyyy-mm-dd') a from emp where empno = 7369 
union 
select empno, 'MGR', to_char(mgr) from emp where empno = 7369 
union 
select empno, 'SAL', to_char(sal) from emp where empno = 7369 
union 
select empno, 'COMM', to_char(comm) from emp where empno = 7369 
union 
select empno, 'DEPTNO', to_char(deptno) from emp where empno = 7369; 
分析:使用UNION 可以将多次查询结果连接起来,要注意,每条查询的列的个数和数据类
型必须一致。因此在查询时都使用了to_char 函数将第二列同一转换为字符型。 
Part IV(扩展知识点) 
01. 分级查询 

Oracle 提供其他数据库没有的分级查询操作: 
例如:希望通过一次查询以树状结构显示EMP 表中的所有上下级关系 
select level, lpad(' ',level-1) || empno empno, ename, mgr,deptno from emp start with 
empno=7839 connect by prior empno = mgr; 
level 是ORACLE 关键字表示分级级别,其中lpad(' ',level-1) 函数是根据level 的值生成
level-1 个空格 
又如:希望通过某个员工回溯它的所有上级,包括上级的上级 
select level, lpad(' ',level-1) || empno, ename, mgr, deptno from emp start with empno=7369 
connect by empno = prior mgr; 
02. CUBE(立方查询) 
如果想统计EMP 表中的所有职员数,每个部门的职员数,每种职位的职员数,每个部
门每种职位的职员数,使用普通分组查询需要查询4 次。但利用ORACLE 的增强语法, 
可以非常方便的完成此类查询(经常用于生成报表) 
例如: 

select count(*),deptno,job,grouping_id(deptno,job) from emp group by cube (deptno,job) 
order by grouping_id(deptno,job) ; 
可以生成如下形式的报表: 
03. 如何考察查询效率 
1) 在SQL-PLUS 中执行 set autotrace on explain 
2) 执行查询 
返回结果中cost(成本)与bytes(字节数)都是越低越好。 
04. 闪回查询 
ORACLE 的特点还有能够更快速的恢复之前误操作的数据,这是通过闪回日志完成的 
例如:查询20 分钟之前的emp 表 
select * from emp as of timestamp sysdate - interval '20' minute; 
再如:恢复5 分钟之前的7369 号员工姓名 
update emp e set ename = 
(select ename from emp 

as of timestamp systimestamp - interval '5' minute where empno=e.empno ) 
where empno=7369; 
甚至可以查询及恢复被删除的表 
select * from user_recyclebin; 
flashback table 表 to before drop; 
05. 正则表达式 
ORACLE 在建表或查询时提供正则表达式支持: 
例如:查询名字以S 作为开头字母的员工 
select * from emp where regexp_like(ename ,'^S'); 
例如:替换电话号码显示方式 
select regexp_replace('123.321.1234', '([0-9]{3})\.([0-9]{3})\.([0-9]{4})', '(\1) \2-\3') 
from dual; 
例如:取得email 地址中的用户名 
select regexp_substr( '[email protected]' , '^[^@]+') from dual; 
例如:取得email 地址中的域名 
select regexp_substr( '[email protected]' , '[^@]+$') from dual; 
06. 如何加注释 
建表时给表和列加注释是一个比较好的数据库编程习惯 
例如:表加注释 
comment on table 表 is '表注释'; 
例如:列加注释 
comment on column 表.列 is '列注释'; 
例如:查表注释 

select * from user_tab_comments where table_name = 表名; 
例如:查列注释 
select * from user_col_comments where table_name = 表名; 

你可能感兴趣的:(Oracle)