一、简介

本文是总结Oracle查询优化方法与改写技巧的第三篇文章,接着第二篇文章,继续。。。

二、优化技巧

【1】日期加减运算方法

--日期加减
select sysdate as today, --今天sysdate + 1 as tomorrow, --明天sysdate - 1 as yesterday, --昨天add_months(sysdate, 1 * 12) as nextyear, --明年add_months(sysdate, -1 * 12) as previousyear --去年from dual;

【2】时、分、秒日期加减运算方法

--时分秒加减  一天24小时,   1小时 =  1 / 24  1分钟 = 1 / 24 /60  1秒钟 = 1 / 24 / 60 / 60
with temp as(select sysdate as now from dual)
select t.now,(t.now + 1 / 24) as next_hours, --后一小时(t.now - 2 / 24) as prev_two_hours, --前两小时(t.now + 1 / 24 / 60) as next_minute, --后一分钟(t.now - 2 / 24 / 60) as prev_two_minutes, --前两分钟(t.now + 10 / 24 / 60 / 60) as next_second, --后10秒(t.now - 20 / 24 / 60 / 60) as prev_two_seconds --前20秒from temp t;

【3】时间间隔计算方法

小时、分钟时间间隔计算方法:

with temp as(select sysdate as time1, sysdate + 20 as time2 from dual)
--计算天、小时、分钟间隔时间
select t.time1,t.time2,(t.time2 - t.time1) as nm,  --相隔天数(t.time2 - t.time1) * 24 as hours,  --相隔的小时数(t.time2 - t.time1) * 24 * 60 as minutes  --相隔的分钟数from temp t;

年、月、日时间间隔计算方法:

with temp as(select sysdate as time1, sysdate + 20 as time2 from dual)
--计算时、分、秒间隔时间
select t.time1,t.time2,(t.time2 - t.time1) as days, --相隔的天数months_between(t.time2, t.time1) as mons, --相隔的月份数months_between(t.time2, t.time1) / 12 as years --相隔的年份数from temp t;

【4】计算一年中周内各日期的次数

这个问题就是统计一年中有多少个星期一、多少个星期二、、、

(a)第一步:统计一年中有多少天

select r.startdate, r.enddate, (r.enddate - r.startdate) as daysfrom (select trunc(sysdate, 'yy') as startdate,add_months(trunc(sysdate, 'yy'), 12) as enddatefrom dual) r

(b)第二步:生成日期列表

select (tt.startdate + (level - 1)) as rqfrom (select r.startdate, r.enddate, (r.enddate - r.startdate) as daysfrom (select trunc(sysdate, 'yy') as startdate,add_months(trunc(sysdate, 'yy'), 12) as enddatefrom dual) r) tt
connect by level <= tt.days

(c)第三步:转换每天对应的周几

select (tt.startdate + (level - 1)) as rq,to_char(tt.startdate + (level - 1), 'DY') as weeksfrom (select r.startdate, r.enddate, (r.enddate - r.startdate) as daysfrom (select trunc(sysdate, 'yy') as startdate,add_months(trunc(sysdate, 'yy'), 12) as enddatefrom dual) r) tt
connect by level <= tt.days

(d)第四步:统计数量

select count(*), ttt.weeksfrom (select (tt.startdate + (level - 1)) as rq,to_char(tt.startdate + (level - 1), 'DY') as weeksfrom (select r.startdate,r.enddate,(r.enddate - r.startdate) as daysfrom (select trunc(sysdate, 'yy') as startdate,add_months(trunc(sysdate, 'yy'), 12) as enddatefrom dual) r) ttconnect by level <= tt.days) tttgroup by ttt.weeks

【5】确定当前记录与下一条记录日期的间隔时间

要实现这个查询,要使用到lead() over()分析函数  或者 lag() over()函数:

--计算当前记录与下一条、上一条日期字段的时间间隔
select e.empno,e.ename,e.deptno,lag(e.hiredate) over(order by e.hiredate) as pre_hiredate, --上一条记录e.hiredate,lead(e.hiredate) over(order by e.hiredate) as next_hiredate --下一条记录from emp e

统计间隔天数:

--计算当前记录与下一条、上一条日期字段的时间间隔
select r.ename,r.deptno,r.pre_hiredate,r.hiredate,r.next_hiredate,(r.next_hiredate - r.pre_hiredate) as daysfrom (select e.empno,e.ename,e.deptno,lag(e.hiredate) over(order by e.hiredate) as pre_hiredate, --上一条记录e.hiredate,lead(e.hiredate) over(order by e.hiredate) as next_hiredate --下一条记录from emp e) r

【6】常用的一些日期函数

(a) to_char()函数:

select sysdate,to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss') as 格式化日期, --当前日期to_char(sysdate, 'yyyy') as 当前年份, --当前年份to_char(sysdate, 'mm') as 当前月份, --当前月份to_char(sysdate, 'dd') as 当前天, --当前天to_char(sysdate, 'hh24') as 当前小时数, --当前小时数to_char(sysdate, 'mi') as 当前分钟数, --当前分钟数to_char(sysdate, 'ss') as 当前秒数, --当前秒数to_char(sysdate, 'ddd') as 年内第几天, --年内第几天to_char(sysdate, 'month') as 月份, --月份to_char(sysdate, 'day') as 当前星期几, --星期几to_char(sysdate, 'dy') as 当前星期几, --星期几from dual

(b)trunc()函数:

select sysdate,trunc(sysdate, 'dd') as 当天, --当天trunc(sysdate, 'day') as 周初, --周初trunc(sysdate, 'mm') as 月初, --月初 trunc(sysdate, 'yy') as 年初, --年初add_months(trunc(sysdate, 'mm'), 1) as 下月初, --下月初last_day(sysdate) as 月末 --月末from dual

注意:last_day()返回的时间的时分秒与日期的相同,不建议使用它作为时间区间条件的计算,假如要计算一个月内的所有时间,可以先计算这个月的月初与下个月的月初,然后计算两者之间的时间即可。sql如下:

select r.startdate, r.enddate, r.time1from (select sysdate,to_date('2019-1-5 00:00:00', 'yyyy-mm-dd hh24:mi:ss') as time1,trunc(sysdate, 'mm') as startdate,add_months(trunc(sysdate, 'mm'), 1) as enddatefrom dual) rwhere r.time1 >= r.startdate --月初and r.time1 < r.enddate --下月初

(c) extract()函数:可以提取时间字段中的年、月、日、时、分、秒等,返回的是number类型的结果。

--extract()可以提取日期中的年、月、日、时、分、秒,但是提取时、分、秒必须使用时间戳才能获取
select extract(year from sysdate) as y, --年extract(month from sysdate) as m, --月extract(day from sysdate) as d, --日extract(hour from systimestamp) as h, --时extract(minute from systimestamp) as m, --分extract(second from systimestamp) as s --秒from dual;

注意点:提取时、分、秒必须要是时间戳类型的日期才能提取,否则报错,如下图:

【7】确定一年是否是闰年

思路:计算中二月份最后一天是28号还是29号就知道是闰年还是平年。

(1)第一步:获取今年年初的日期:

select trunc(sysdate, 'yyyy') as y,
from dual

(2)第二步:获取二月份的第一天日期和最后一天的日期:

select t.y,add_months(t.y, 1) as firstday,last_day(add_months(t.y, 1)) as lastdayfrom (select trunc(sysdate, 'yyyy') as y from dual) t

(3)第三步:得到二月份最后一天日期的日,判断是否是28或者29即可:

--计算今年是闰年还是平年
select t2.y,t2.firstday,t2.lastday,to_char(t2.lastday, 'dd') as rq,decode(to_number(to_char(t2.lastday, 'dd')), 28, '平年', '闰年') as rpnfrom (select t.y,add_months(t.y, 1) as firstday,last_day(add_months(t.y, 1)) as lastdayfrom (select trunc(sysdate, 'yyyy') as y from dual) t) t2

【8】确定一年内属于周内某一天的所有日期

案例:要求返回本年度所有星期五的所有日期:

(1)第一步:使用connect by ..构造全年的日期:

--统计本年度所有周五的日期
select t2.startdate + (level - 1) as rq --因为要从第一天开始统计,所有要减一from (select t.startdate, t.enddate, (t.enddate - t.startdate) as cnt --全年天数from (select trunc(sysdate, 'yy') as startdate,add_months(trunc(sysdate, 'yy'), 12) as enddatefrom dual) t) t2
connect by level <= t2.cnt

(2)第二步:使用to_cahr(xxx,'d') = 6统计周五的日期:

--统计本年度所有周五的日期
select t4.rqfrom (select t3.rq, to_number(to_char(t3.rq, 'd')) as nmfrom (select t2.startdate + (level - 1) as rq --因为要从第一天开始统计,所有要减一from (select t.startdate,t.enddate,(t.enddate - t.startdate) as cnt --全年天数from (select trunc(sysdate, 'yy') as startdate,add_months(trunc(sysdate, 'yy'), 12) as enddatefrom dual) t) t2connect by level <= t2.cnt) t3) t4where t4.nm = 6

.

  • 注意点:统计星期几的时候,尽量使用to_char(xxx,'d'),因为to_char(xxx,'day')会受不同的字符集影响,返回的结果也不相同,所以使用to_char(xxxx,'d')可以避免因为字符集对结果集的影响。

【9】创建本月日历

(1)第一步:使用connect by ... 列出当前月份的所有日期

select t2.startdate + (level - 1) as rqfrom (select t.startdate, t.enddate, (t.enddate - t.startdate) + 1 as tsfrom (select trunc(sysdate, 'mm') as startdate,last_day(trunc(sysdate, 'mm')) as enddatefrom dual) t) t2
connect by level <= t2.ts

(2)第二步:统计每天日期对应第一周、星期几等数据

select t3.rq,to_char(t3.rq, 'iw') as a, --属于第几周to_char(t3.rq, 'dd') as b, --日号to_number(to_char(t3.rq, 'd')) as c --周几  5 - 周四  6 - 周五 。。。 from (select t2.startdate + (level - 1) as rqfrom (select t.startdate,t.enddate,(t.enddate - t.startdate) + 1 as tsfrom (select trunc(sysdate, 'mm') as startdate,last_day(trunc(sysdate, 'mm')) as enddatefrom dual) t) t2connect by level <= t2.ts) t3

(3)第三步:行转列显示本月日历

--本月日历
select max(casewhen t4.c = 2 thent4.belsenullend) as 周一,max(casewhen t4.c = 3 thent4.belsenullend) as 周二,max(casewhen t4.c = 4 thent4.belsenullend) as 周三,max(casewhen t4.c = 5 thent4.belsenullend) as 周四,max(casewhen t4.c = 6 thent4.belsenullend) as 周五,max(casewhen t4.c = 7 thent4.belsenullend) as 周六,max(casewhen t4.c = 1 thent4.belsenullend) as 周日from (select t3.rq,to_char(t3.rq, 'iw') as a, --属于第几周to_char(t3.rq, 'dd') as b, --日号to_number(to_char(t3.rq, 'd')) as c --周几  5 - 周四  6 - 周五 。。。 from (select t2.startdate + (level - 1) as rqfrom (select t.startdate,t.enddate,(t.enddate - t.startdate) + 1 as tsfrom (select trunc(sysdate, 'mm') as startdate,last_day(trunc(sysdate, 'mm')) as enddatefrom dual) t) t2connect by level <= t2.ts) t3) t4group by t4.aorder by t4.a

【10】计算指定年份的各个季度的开始时间和结束时间

思路:我们画一张图分析下要怎么统计出各个季度的起止时间:

通过上面的分析,就可以比较简单的写出下面的sql了:

(1)第一步:

select t.year, level as jd --当前第几季度from (select to_date(extract(year from sysdate), 'yyyy') as year from dual) t
connect by level <= 4

(2)第二步:

select t2.year,t2.jd,add_months(t2.year, (t2.jd - 1) * 3) as startdate,add_months(t2.year, (t2.jd * 3)) as 季度结束时间的后一天,add_months(t2.year, (t2.jd * 3)) - 1 as enddatefrom (select t.year, level as jd --当前第几季度from (select to_date(extract(year from sysdate), 'yyyy') as yearfrom dual) tconnect by level <= 4) t2

【11】按照给定的时间单位过滤数据

案例:要求返回2月或12月聘用的员工信息,以及周二聘用的员工信息

思路:利用前面讲的to_char()函数将员工的聘用日期提取相应的月份信息、周几信息,然后再过滤即可。相应的sql如下:

select t.hiredate, t.sal, t.ename, t.month, t.week, t.weekstrfrom (select e.hiredate,e.ename,e.sal,to_char(e.hiredate, 'mm') as month,to_number(to_char(e.hiredate, 'd')) as week, --注意要使用‘d’,这样不会受字符集影响to_char(e.hiredate, 'day') as weekstrfrom emp e) twhere t.month in ('02', '12') -- in ('2','12')  与  in ('02','12')的区别,查询结果都不一样or t.week = 3order by t.hiredate

【12】按指定间隔汇总数据

思路:使用to_char()和trunc()函数提取日期中的分钟之初以及算出每隔多少分钟的起始时间即可。

(1)第一步:构造测试数据

with temp as(select to_date('2019-01-06 20:38:20', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:48:50', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:37:10', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:44:00', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:56:15', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dual)
select * from temp;

(2)第二步:提取相应的分钟数等

with temp as(select to_date('2019-01-06 20:38:20', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:48:50', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:37:10', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:44:00', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:56:15', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dual)
select trunc(t.times, 'mi') as starttime,to_char(t.times, 'mi') as minutes,t.namesfrom temp t;

(3)第三步:计算相隔十分钟的时间并分组分别统计数量。

with temp as(select to_date('2019-01-06 20:38:20', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:48:50', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:37:10', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:44:00', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dualunion allselect to_date('2019-01-06 20:56:15', 'yyyy-mm-dd hh24:mi:ss') as times,'a' as namesfrom dual)
select t3.mins, count(*)from (select t2.minsfrom (select trunc(t.times, 'mi') -mod(to_char(t.times, 'mi'), 10) / 24 / 60 as mins --每隔十分钟from temp t) t2) t3group by t3.minsorder by t3.mins

【13】定位连续值的范围

案例:查询时间连续的数据

首先构造测试数据:

--定位连续值的范围
with temp as(select 1 as id,to_date('2019-1-1', 'yyyy-mm-dd') as startdate,to_date('2019-1-2', 'yyyy-mm-dd') as enddatefrom dualunion allselect 2 as id,to_date('2019-1-2', 'yyyy-mm-dd') as startdate,to_date('2019-1-3', 'yyyy-mm-dd') as enddatefrom dualunion allselect 3 as id,to_date('2019-1-3', 'yyyy-mm-dd') as startdate,to_date('2019-1-4', 'yyyy-mm-dd') as enddatefrom dualunion allselect 4 as id,to_date('2019-1-5', 'yyyy-mm-dd') as startdate,to_date('2019-1-6', 'yyyy-mm-dd') as enddatefrom dualunion allselect 5 as id,to_date('2019-1-6', 'yyyy-mm-dd') as startdate,to_date('2019-1-7', 'yyyy-mm-dd') as enddatefrom dualunion allselect 6 as id,to_date('2019-1-8', 'yyyy-mm-dd') as startdate,to_date('2019-1-9', 'yyyy-mm-dd') as enddatefrom dual)
select t.id, t.startdate, t.enddate from temp t;

如上,需要查询出符合enddate = 下一条记录.startdate的数据:

(1)第一种方法:使用自连接来实现:

--定位连续值的范围
with temp as(select 1 as id,to_date('2019-1-1', 'yyyy-mm-dd') as startdate,to_date('2019-1-2', 'yyyy-mm-dd') as enddatefrom dualunion allselect 2 as id,to_date('2019-1-2', 'yyyy-mm-dd') as startdate,to_date('2019-1-3', 'yyyy-mm-dd') as enddatefrom dualunion allselect 3 as id,to_date('2019-1-3', 'yyyy-mm-dd') as startdate,to_date('2019-1-4', 'yyyy-mm-dd') as enddatefrom dualunion allselect 4 as id,to_date('2019-1-5', 'yyyy-mm-dd') as startdate,to_date('2019-1-6', 'yyyy-mm-dd') as enddatefrom dualunion allselect 5 as id,to_date('2019-1-6', 'yyyy-mm-dd') as startdate,to_date('2019-1-7', 'yyyy-mm-dd') as enddatefrom dualunion allselect 6 as id,to_date('2019-1-8', 'yyyy-mm-dd') as startdate,to_date('2019-1-9', 'yyyy-mm-dd') as enddatefrom dual)
--采用自关联查询连续的数据
select t1.id, t1.startdate, t1.enddatefrom temp t1, temp t2where t1.enddate = t2.startdate;

(2)第二种方法:使用分析函数来实现:

--定位连续值的范围
with temp as(select 1 as id,to_date('2019-1-1', 'yyyy-mm-dd') as startdate,to_date('2019-1-2', 'yyyy-mm-dd') as enddatefrom dualunion allselect 2 as id,to_date('2019-1-2', 'yyyy-mm-dd') as startdate,to_date('2019-1-3', 'yyyy-mm-dd') as enddatefrom dualunion allselect 3 as id,to_date('2019-1-3', 'yyyy-mm-dd') as startdate,to_date('2019-1-4', 'yyyy-mm-dd') as enddatefrom dualunion allselect 4 as id,to_date('2019-1-5', 'yyyy-mm-dd') as startdate,to_date('2019-1-6', 'yyyy-mm-dd') as enddatefrom dualunion allselect 5 as id,to_date('2019-1-6', 'yyyy-mm-dd') as startdate,to_date('2019-1-7', 'yyyy-mm-dd') as enddatefrom dualunion allselect 6 as id,to_date('2019-1-8', 'yyyy-mm-dd') as startdate,to_date('2019-1-9', 'yyyy-mm-dd') as enddatefrom dual)
--采用分析函数查询连续的数据(效率高)
select t2.id, t2.startdate, t2.enddatefrom (select t1.id,t1.startdate,t1.enddate,lead(t1.startdate) over(order by t1.id) as next_startdate --取出下一条记录的开始时间from temp t1) t2where t2.next_startdate = t2.enddate --下一条记录的开始时间 = 该条记录的结束时间

【14】查找同一组或分区中行之间的差

案例:要求计算同一个人本次登录时间与下一次登录时间相隔的时间差

首先构造测试数据:

--查找同一组或分区中行之间的差--案例:要求计算同一个人本次登录时间与下一次登录时间相隔的时间差
with temp as(select 'a' as name,to_date('2019-01-01 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'b' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'a' as name,to_date('2019-01-01 18:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'b' as name,to_date('2019-01-02 12:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'a' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dual)
select * from temp t order by t.name, t.logindate

(1)第一种方法:使用rownum + 自连接的方法:

--查找同一组或分区中行之间的差--案例:要求计算同一个人本次登录时间与下一次登录时间相隔的时间差
with temp as(select 'a' as name,to_date('2019-01-01 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'b' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'a' as name,to_date('2019-01-01 18:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'b' as name,to_date('2019-01-02 12:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'a' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dual),
temp2 as(select t2.*, rownum as rnfrom (select t.* from temp t order by t.name, t.logindate) t2)
select t1.name,t1.logindate, --本次登录时间t2.logindate as nextlogindate, --下一次登录时间(t2.logindate - t1.logindate) * 24 * 60 as minutes, --相隔的分钟数t1.rn as 上一次rownum,t2.rn as 下一次rownumfrom temp2 t1left join temp2 t2on t1.name = t2.nameand t2.rn = t1.rn + 1;

(2)第二种方法:使用lead() over()分析函数实现:

--查找同一组或分区中行之间的差--案例:要求计算同一个人本次登录时间与下一次登录时间相隔的时间差
with temp as(select 'a' as name,to_date('2019-01-01 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'b' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'a' as name,to_date('2019-01-01 18:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'b' as name,to_date('2019-01-02 12:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dualunion allselect 'a' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as logindatefrom dual),
temp2 as(select t2.* from (select t.* from temp t order by t.name, t.logindate) t2)
select t2.name,t2.logindate,t2.nextlogindate,(t2.nextlogindate - t2.logindate) * 24 * 60 as minutes --使用分析函数效率比较高from (select t1.name,t1.logindate, --本次登录时间lead(t1.logindate) over(partition by t1.name order by t1.logindate) as nextlogindate --按名称分区进行统计from temp2 t1) t2

【15】模拟打卡记录,统计员工的上班时间和下班时间

--案例:统计每个员工的上班时间和下班时间--以下是模拟打卡记录
with temp as(select 'a' as name,to_date('2019-01-01 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'a' as name,to_date('2019-01-01 09:10:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'a' as name,to_date('2019-01-01 12:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'a' as name,to_date('2019-01-01 18:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'a' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'a' as name,to_date('2019-01-02 12:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'a' as name,to_date('2019-01-02 12:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'b' as name,to_date('2019-01-01 10:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'b' as name,to_date('2019-01-01 18:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'b' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'b' as name,to_date('2019-01-02 18:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dualunion allselect 'c' as name,to_date('2019-01-02 09:00:00', 'yyyy-mm-dd hh24:mi:ss') as timefrom dual)
--统计每个员工的上班时间和下班时间
select t2.name, min(t2.time) as sbsj, max(t2.time) as xbsj, t2.rqfrom (select t.name, t.time, trunc(t.time, 'dd') as rq from temp t) t2group by t2.rq, t2.name;

【16】给结果集分页

oracle分页是基于rownum来进行分页的。

案例:假设每页显示5条数据,需要查询emp表6-10条的数据

(1)第一步:按员工工资排好序

--第一步:按员工工资排好序
select e.empno, e.ename, e.salfrom emp ewhere e.sal is not nullorder by e.sal

(2)第二步:排序后生成rownum排序号

--第二步:排序后生成rownum排序号
select rownum as pxh, t.empno, t.ename, t.salfrom (select e.empno, e.ename, e.salfrom emp ewhere e.sal is not nullorder by e.sal) twhere rownum <= 10

(3)第三步:使用嵌套子查询过滤rownum >= 6的数据

--第三步:使用嵌套子查询过滤rownum >= 6的数据
select t2.pxh, t2.empno, t2.ename, t2.salfrom (select rownum as pxh, t.empno, t.ename, t.salfrom (select e.empno, e.ename, e.salfrom emp ewhere e.sal is not nullorder by e.sal) twhere rownum <= 10) t2where t2.pxh >= 6

  • 注意点1:必须要排好序之后再次生成rownum作为排序号才正确
--注意:必须要排好序之后再次生成rownum作为排序号才正确
select rownum as pxh2, t2.pxh1, t2.empno, t2.ename, t2.salfrom (select rownum as pxh1, e.empno, e.ename, e.salfrom emp ewhere e.sal is not nullorder by e.sal) t2

  • 注意点2:必须在外层使用rownum >= 6才能正确过滤数据,不能使用rownum >= 6 and rownum <= 10进行判断,因为rownum是先有数据才能生成的
--注意:必须在外层使用rownum >= 6才能正确过滤数据,不能使用rownum >= 6 and rownum <= 10进行判断,因为rownum是先有数据才能生成的
select count(*) from emp e;--错误使用方法
select count(*)  --统计不出数据from emp ewhere rownum <= 10and rownum >= 6;--正确使用方法
select count(*)from (select rownum as rn --先有数据rownum才能确定是多少from (select * from emp e where e.sal is not null order by e.sal) t) t2where t2.rn <= 10and t2.rn >= 6;

【17】跳过n条记录

案例:隔行返回emp表的数据

select t4.*from (select t3.*, mod(t3.rn, 3) as flag --第三步:使用求余函数mod(rn,x)隔x行显示 from (select rownum as rn, t2.* --第二步:使用rownum生成排序号from (select * from emp e order by e.ename) t2) t3) t4 --第一步: 排序where t4.flag = 1 --第四步:数据过滤,实际项目中根据需求随机抽取样本数据

【18】找到包含最大值和最小值的记录

案例:查询工资最高和最低的员工信息

(1)第一种方法:使用min()/max()函数实现

select *from emp ewhere e.sal in (select min(sal) as salfrom emp eunion allselect max(sal) as salfrom emp e)

(2)第二种方法:使用min() over() / max() over()分析函数实现

select *from (select e.sal,e.ename,e.empno,e.hiredate,e.deptno,e.job,e.mgr,min(sal) over() as minsal,max(sal) over() as maxsalfrom emp e) twhere t.sal in (t.minsal, t.maxsal)

【19】行转列

oracle中可以使用case when then end子句和oracle 11g之后新增的pivot函数来实现行转列功能

案例:对emp表按job分组,每个部门显示为一列

(1)第一种方法:使用case when then end子句实现

--行转列实现 case when then end..
select e.job,sum(casewhen e.deptno = 10 thene.salelsenullend) as dept_10_sal,sum(casewhen e.deptno = 20 thene.salelsenullend) as dept_20_sal,sum(casewhen e.deptno = 30 thene.salelsenullend) as dept_30_sal,sum(e.sal) as totalsalfrom emp egroup by e.job

(2)第二种方法:使用pivot()函数实现

--行转列实现 pivot
--pivot无法实现合计工资的计算
--pivot只能按照一个条件进行分组统计,如果需要按照两个条件进行统计的话只能使用case when then end..子句
select *from (select e.deptno, e.sal, e.job from emp e)
pivot(sum(sal) as salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30))

  • 注意点1:pivot无法实现合计工资的计算。
  • 注意点2:pivot只能按照一个条件进行分组统计,如果需要按照两个条件进行统计的话只能使用case when then end..子句。
  • 注意点3:pivot使用场景比较局限,而case when then end子句适用场景比较广,实际项目中,根据具体需求选择其中一种方式实现即可。

【20】列转行

首先构造测试数据:

with temp as(select *from (select e.deptno, e.sal from emp e)pivot(count(*) as cnt, sum(sal) as dept_salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30)))
select * from temp;

案例:要求三个部门的次数分为一列进行展示

(1)第一种方法:使用union all实现,但是这种方式效率不是特别高,尤其是字段多数据量大的情况下。

with temp as(select *from (select e.deptno, e.sal from emp e)pivot(count(*) as cnt, sum(sal) as dept_salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30)))
--select * from temp;
select 10 as deptno, dept_10_cnt as salfrom temp t
union all
select 20 as deptno, dept_20_cnt as salfrom temp t
union all
select 30 as deptno, dept_30_cnt as sal from temp t

(2)第二种方法:使用unpivot函数实现

with temp as(select *from (select deptno, sal from emp e)pivot(count(*) as cnt, sum(sal) as dept_salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30)))
select deptno, cntfrom temp t unpivot(cnt for deptno in(dept_10_cnt,dept_20_cnt,dept_30_cnt));

注意:unpivot同样只能处理一个条件,如果同时需要将工资和人次都转为一列显示。只能分别转换后再进行join连接即可。

思路:可以先拆分为两个分别按工资、人次unpivot的sql,然后再进行join

--按人次统计
with temp as(select *from (select deptno, sal from emp e)pivot(count(*) as cnt, sum(sal) as dept_salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30)))
select substr(deptno, 0, 7) as deptno, cntfrom temp t unpivot include nulls(cnt for deptno in(dept_10_cnt,dept_20_cnt,dept_30_cnt));--按工资统计
with temp as(select *from (select deptno, sal from emp e)pivot(count(*) as cnt, sum(sal) as dept_salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30)))
select substr(deptno, 0, 7) as deptno, salfrom temp t unpivot(sal for deptno in(dept_10_dept_sal,dept_20_dept_sal,dept_30_dept_sal));

下面的sql就是内连接之后的sql:

with temp as(select *from (select deptno, sal from emp e)pivot(count(*) as cnt, sum(sal) as dept_salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30)))
select a.deptno, a.cnt, b.salfrom (select substr(deptno, 0, 7) as deptno, cntfrom temp t unpivot include nulls(cnt for deptno in(dept_10_cnt,dept_20_cnt,dept_30_cnt))) ainner join (select substr(deptno, 0, 7) as deptno, salfrom temp t unpivot include nulls(sal for deptno in(dept_10_dept_sal,dept_20_dept_sal,dept_30_dept_sal))) bon a.deptno = b.deptno

如果不想使用内连接,也可以同时使用两次unpivot,但是主要要加上过滤条件,因为两次unpivot之后的是笛卡尔积。

--两个unpivot返回的笛卡尔积,上面的unpivot之后再进行unpivot
with temp as(select *from (select deptno, sal from emp e)pivot(count(*) as cnt, sum(sal) as dept_salfor deptno in(10 as dept_10, 20 as dept_20, 30 as dept_30)))
select *from (select substr(deptno1, 0, 7) as deptno1,substr(deptno2, 0, 7) as deptno2,cnt,salfrom temp t unpivot include nulls(cnt for deptno1 in(dept_10_cnt,dept_20_cnt,dept_30_cnt)) unpivot(sal for deptno2 in(dept_10_dept_sal,dept_20_dept_sal,dept_30_dept_sal)))where deptno1 = deptno2

三、总结

这是第三部分的总结以及一些案例,后续会接着总结。积少成多,keeping....

Oracle查询优化改写技巧与案例总结三相关推荐

  1. 2016.9.9《Oracle查询优化改写技巧与案例》电子工业出版社一书中的技巧

    1.coalesce (c1,c2,c3,c4,...) 类似于nvl但可以从多个表达式中返回第一个不是null的值 2.要在where条件中引用列的别名,可以再嵌套一层查询 select * fro ...

  2. 【书评:Oracle查询优化改写】第三章

    [书评:Oracle查询优化改写]第三章 BLOG文档结构图 一.1 导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① 隐含参数 _ ...

  3. 【书评:Oracle查询优化改写】第四章

    [书评:Oracle查询优化改写]第四章 [书评:Oracle查询优化改写]第四章 BLOG文档结构图 导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O ...

  4. 【书评:Oracle查询优化改写】第14章 结尾章

    [书评:Oracle查询优化改写]第14章 结尾章 一.1  相关参考文章链接 前13章的链接参考相关连接: [书评:Oracle查询优化改写]第一章 http://blog.itpub.net/26 ...

  5. 只是简单读了读《oracle查询优化改写》,就让我获益匪浅,想写好sql,这一本书就够了!

    目录 写在前面 基础知识 空值 返回前几行 获取随机数 like 排序 union 分页(6-10条) 表关联 复制表 日期 日期加减 trunc对于日期的用法 获取时间 判断是否是闰年(只需要判断二 ...

  6. oracle书评,【书评:Oracle查询优化改写】第二章

    BLOG文档结构图 在上一篇中http://blog.itpub.net/26736162/viewspace-1652985/,我们主要分析了一些单表查询的时候需要注意的内容,今天第二章也很简单,主 ...

  7. Oracle查询优化改写2.0 第二章:给查询结果排序

    ------chapter2给查询结果排序 --2.1以指定的次序返回查询结果  order by xxx asc/desc select empno,ename,hiredate from emp ...

  8. 视频教程-Oracle数据库开发技巧与经典案例讲解一-Oracle

    Oracle数据库开发技巧与经典案例讲解一 Oracle DBA,熟悉Unix操作系统,精通Oracle数据库. 曾任职某大型金融IT公司,负责银行领域数据库构建与运维,维护大量银行数据库系统.目前在 ...

  9. 【SQL开发实战技巧】系列(三):SQL排序的那些事

    系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...

  10. 【SQL开发实战技巧】系列(三十七):数仓报表场景☞从表内始终只有近两年的数据,要求用两列分别显示其中一年的数据聊行转列隐含信息的重要性

    系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...

最新文章

  1. 人工智能也能写出如此诗句
  2. Oracle优化06-Hint
  3. BigData:绘制2018年福布斯中国富豪榜人名坐标地图(解决多个人名显示在同一个家乡地点)
  4. 数字化时代,TO B业务如何进阶?
  5. 【迁移学习】隐私保护下的迁移算法
  6. matlab 配置mex 识别vs2015
  7. 回顾一年的工作历程_【设备管理公司】召开20202021年度总结计划表彰暨工作述职会议...
  8. python导出csv有引号_python – csv中的双引号元素不能用pandas读取
  9. C# winform程序怎么打包成安装项目(图解)
  10. Android 系统(124)---Android 如何快速写满存储空间
  11. Solr相关概念详解:SolrRequestHandler
  12. Ruby之散列与快排小程序
  13. spring mvc入门案例
  14. html旋转音乐图标播放器,css特效之旋转音乐播放器
  15. 很邪门的事,你知道多少?
  16. 成功解决 ARP项添加失败:请求的操作需要提升
  17. 武侠乂 兵器招式和高级心法介绍
  18. 华为“More Bits, Less Watts”新践行
  19. Ant工具 ant的安装与配置 ant作用
  20. Android数据恢复工具

热门文章

  1. java map存储格式_java HashMap HashSet的存储方式
  2. 大教堂与集市 The Cathedral The Bazaar -- 这是当代软件技术领域最重要的著作
  3. 算法:回溯十四 Restore IP Addresses数字字符串还原为IP地址(2种解法)
  4. PyTorch搭建AlexNet模型(在CIFAR10数据集上准确率达到了85%)
  5. 2021-10-1825. K 个一组翻转链表
  6. 591. 标签验证器
  7. boost库BOOST_FOREACH使用说明
  8. Kruskal算法实现最小生成树MST(java)
  9. Oauth三种认证方式
  10. 力扣题目算法分类【持续更新】