分段统计与Oracle的分析函数、逻辑判断等知识点的综合运用
重点部分:TOTAL层
项目要求:
统计每个巡检员(USER_ID)当前月的签到率及查询相关字段
签到率公式:以巡检员为单位,
(当月至今天为止签到的所有点/该月巡检点的总个数)=(b.Point/a.Total)
TOTAL相关更多要求:
①在使用过程中,巡检点个数会根据实际情况进行变更,例如2014年1月15日管理员给某一位巡检员安排5个点,10天后增加了2个点,即2014年1月24日以后按照7个点来统计,所以,如果当月更改多次的话,这个月会分成许多段。
②每一次更改有一个批次号,对应表T_SIGN_POINTS的POINT_DATE字段,这个字段的值设置为明天生效,即今日更改,明天生效。
③TOTAL公式:
假设有以下已知条件:
假设今天是2014年4月25日,USER_ID=1249,统计2014年4月的该巡检员应该巡查的点的总个数,T_SIGN_POINTS中相关批次有Batch2014.3.29=6个、Batch2014.4.5=8个,Batch2014.4.12=11个、Batch2014.4.26日=5个
则TOTAL的数学计算应该为:6个×4天+8个×11天+11个×13天=255个(另有其它一些边界情况类似)
这里需要考虑到多种可能性,即便只使用最理想的情况,将上面这个公式完全使用SQL语言完成,也是一个挺复杂的过程,相比之下,查询需要的字段后在后台做计算难度会降低,不过这种写法可以大幅提升对Oracle或者SQL语言的运用和理解,不排除Oracle有更合适的函数或更简洁的方法来解决类似问题。
相关表及字段情况:
表T_SIGN_RECORDS:记录该巡检员已签到的点
表T_SIGN_POINTS:记录给每个巡检员安排的巡检点
完整SQL语句:
select *
from (select a.*,
case a.IS_LOGIN
when 1 then
'已登录'
when 0 then
'未登录'
else
'未登录'
end as WEB_STATUS,
case a.MOBILE_LOGIN
when 1 then
'已登录'
when 0 then
'未登录'
else
'未登录'
end as MOBILE_STATUS,
b.name as dep_name,
b.parent_id,
b.dep_level,
c.name as role_name,
c.order_index,
c.function_no
from t_user a, t_department b, t_role c
where a.dep_id = b.id
and a.role_id = c.id
and IS_PATROL = 1) x
left join (select a.user_id,
(case
when b.point is null then
0
else
b.point
end) || '/' || (a.total) as rate
from (select user_id, sum(PartNum) as total /*【TOTAL第2层】开始*/
from (select user_id, /*【TOTAL第1层】开始*/
tday,
batchnum,
batchMon,
batchDay,
seg_Next,
decode(seg_next,
null,
decode(greatest(batchday, tday),
tday,
(tday - batchday + 1) *
batchnum,
batchday,
0,
null),
decode(greatest(seg_next, tday),
tday,
(seg_next - batchday) *
batchnum,
seg_next,
(tday - batchday + 1) *
batchnum,
null)) as PartNum
from (SELECT a.user_id,
a.tday,
a.batchnum,
a.batchMon,
a.batchDay,
lead(a.batchDay, 1, null) over(PARTITION BY a.user_id order by a.batchDay, a.batchmon) as seg_Next
FROM (select t.user_id, /*【TOTAL第0层】开始*/
to_char((SELECT EXTRACT(MONTH FROM
sysdate)
FROM DUAL)) as TMon,
(SELECT TRUNC(SYSDATE)
FROM DUAL) as TDay,
decode(sign(months_between(trunc(TO_DATE(t.points_date,
'YYYY-MM-DD'),
'MM'),
trunc(sysdate,
'MM'))),
0,
(select (trunc(TO_DATE(t.points_date,
'YYYY-MM-DD'),
'MM') + 1)
from dual),
-1,
(select trunc(sysdate,
'MM')
from dual),
1,
(select (trunc(TO_DATE(t.points_date,
'YYYY-MM-DD'),
'MM') + 2)
from dual)) as batchMon,
decode(sign(months_between(trunc(TO_DATE(t.points_date,
'YYYY-MM-DD'),
'MM'),
trunc(sysdate,
'MM'))),
-1,
(select trunc(sysdate,
'MM')
from dual),
TO_DATE(t.points_date,
'YYYY-MM-DD')) as batchDay,
count(t.user_id) as BatchNum
from T_SIGN_POINTS t
where (to_date(t.points_date,
'YYYY-MM-DD') -
(select trunc(sysdate, 'MM')
from dual) >= 0)
or (t.points_date =
(select max(points_date) as EarlyMax
from T_SIGN_POINTS
where USER_ID = t.user_id
and (to_date(points_date,
'YYYY-MM-DD') <
(select trunc(sysdate,
'MM')
from dual))
GROUP BY USER_ID))
group by t.user_id, t.points_date
order by t.user_id desc,
batchDay desc,
batchmon desc) A /*【TOTAL第0层】结束*/
order by user_id desc,
batchday desc,
batchmon desc)) /*【TOTAL第1层】结束*/
group by user_id
order by user_id desc) a /*【TOTAL第2层】结束*/
left join (select count(*) as point, user_id
from t_sign_records sr
where to_char(sign_time, 'yyyy-mm') = '2014-05'
group by user_id) b
on a.user_id = b.user_id) y
on x.id = y.user_id
order by order_index, user_id
注释:
【外围层】连接到:用户表(T_USER)、部门表(T_DEPARTMENT)、角色表(T_ROLE)查询一些字段 ,值得注意的是CASE WHEN THEN ELSE END和显示格式“|| '/' ||”的使用
【POINT层】一个简单的count函数的使用
【TOTAL层】是重点,由多层嵌套而成,接下来是各层的结果图片和知识点:
【TOTAL第0层】
结果图片:
SQL功能:查询并统计与当月相关的批次及各批次的点的个数
BatchNum为该批次的点的个数,
TMon为当月的月份,
BatchMon为批次号所在的月份,这个字段的值可以区分批次号是当月的还是本月之前的最大批次号,为的是辅助lead函数进行排序,如果批次号在当月,结果为当月的第2天,如果在这个月之前,返回当月的第1天
【TOTAL第1层】
结果图片:
TOTAL第1层内层结果
TOTAL第1层外层结果:计算每一段的签到点的总个数
SQL功能:主要增加SEG_NEXT字段,将下一个批次号的日期做为SEG_NEXT
lead(a.batchDay, 1, 0) over(PARTITION BY a.user_id order by a.batchDay, a.batchmon) as seg_Next
PARTITION以a.user_id为区域执行lead函数,按照a.batchDay和a.batchmon排序,这里可看出batchmon字段的意义:如果本月在2014年4月1号有批次号,就把小于本月的最大批次号放在4月1号批次号的下面,这样,结果图片中第9行batchDay和SEG_NEXT就都是都是1,两段的差值是0,不会影响后续的结果。
【TOTAL第2层】
结果图片
SQL知识点:
Decode函数:
decode(column,if_value,value,elseif_value,value,default_value); |
Decode函数可以与sigh函数和greatest函数结合使用,其中与greatest函数结合使用的时候,要注意参数的顺序和if_value和value的顺序,因为greatest的参数存在相等的情况,当参数相等的时候,会执行第一个if_value的value
2014-05-09
上面的SQL只能用在统计当月的情况,而且TMon和total最外层where语句有bug,后来项目中增加了一个按时间段查询的功能,将这些bug修正并总结了一个任意时间段通用版本。
接下来的这个SQL可用于任意时间段的情况,只需传入合适的参数即可,其中StartTime对应时间段的起点、EndTime对应时间段的终点,建议格式‘YYYY-MM-DD’,目前的代码使用了“+StartTime+”的形式,这样直接就可以复制粘贴到Flex的SQL字符串中使用,只需变量名一致即可。
select user_id, sum(PartNum) as total
from (select user_id,
tday,
batchnum,
AidSort,
batchDay,
seg_Next,
decode(seg_next,
null,
decode(greatest(batchday, tday),
tday,
(tday - batchday + 1) * batchnum,
batchday,
0,
null),
decode(greatest(seg_next, tday),
tday,
(seg_next - batchday) * batchnum,
seg_next,
(tday - batchday + 1) * batchnum,
null)) as PartNum
from (SELECT a.user_id,
a.tday,
a.batchnum,
a.AidSort,
a.batchDay,
lead(a.batchDay, 1, null) over(PARTITION BY a.user_id order by a.batchDay, a.AidSort) as seg_Next
FROM (select t.user_id,
TO_DATE('"+EndTime+"', 'YYYY-MM-DD') as TDay,
decode((select (case
when TO_DATE(t.points_date,
'YYYY-MM-DD') <
(to_date('"+StartTime+"',
'YYYY-MM-DD')) then
-1
when TO_DATE(t.points_date,
'YYYY-MM-DD') >
TO_DATE('"+EndTime+"',
'YYYY-MM-DD') then
1
else
0
end)
from dual),
-1,
'AS1',
0,
'AS2',
'AS3') as AidSort,
decode((select (case
when TO_DATE(t.points_date,
'YYYY-MM-DD') <
(TO_DATE('"+StartTime+"',
'YYYY-MM-DD')) then
-1
when TO_DATE(t.points_date,
'YYYY-MM-DD') >
TO_DATE('"+EndTime+"',
'YYYY-MM-DD') then
1
else
0
end)
from dual),
-1,
TO_DATE('"+StartTime+"', 'YYYY-MM-DD'),
TO_DATE(t.points_date, 'YYYY-MM-DD')) as batchDay,
count(t.user_id) as BatchNum
from T_SIGN_POINTS t
where (t.points_date =
(select min(points_date) as FutureMin
from T_SIGN_POINTS
where USER_ID = t.user_id
and (to_date(points_date, 'YYYY-MM-DD') >
TO_DATE('"+EndTime+"', 'YYYY-MM-DD'))
GROUP BY USER_ID))
or (to_date(t.points_date, 'YYYY-MM-DD') between
TO_DATE('"+StartTime+"', 'YYYY-MM-DD') and
TO_DATE('"+EndTime+"', 'YYYY-MM-DD'))
or (t.points_date =
(select max(points_date) as EarlyMax
from T_SIGN_POINTS
where USER_ID = t.user_id
and (to_date(points_date, 'YYYY-MM-DD') <
TO_DATE('"+StartTime+"', 'YYYY-MM-DD'))
GROUP BY USER_ID))
group by t.user_id, t.points_date
order by t.user_id desc, batchDay desc, AidSort desc) A
order by user_id desc, batchday desc, AidSort desc))
group by user_id
order by user_id desc
这段SQL代码只是Total层(完整代码中已重命名为a层),如果想嵌入到最上面完整的代码,只需将完整代码的最后的b层更改为如下,其它不变
left join (select count(*) as point, user_id
from t_sign_records sr
where trunc(sign_time) between
TO_DATE('2014-05-05', 'YYYY-MM-DD') and
TO_DATE('2014-05-09', 'YYYY-MM-DD')
group by user_id) b
分段统计与Oracle的分析函数、逻辑判断等知识点的综合运用的更多相关文章
- oracle的分析函数over 及开窗函数
转:http://www.2cto.com/database/201310/249722.html oracle的分析函数over 及开窗函数 一:分析函数over Oracle从8.1.6开 ...
- Oracle 10gR2分析函数
Oracle 10gR2分析函数汇总 (Translated By caizhuoyi 2008‐9‐19) 说明: 1. 原文中底色为黄的部分翻译存在商榷之处,请大家踊跃提意见: 2. 原文中淡 ...
- 超级牛皮的oracle的分析函数over(Partition by...) 及开窗函数 (转)
http://zonghl8006.blog.163.com/blog/static/4528311520083995931317/ over(Partition by...) 一个超级牛皮的ORAC ...
- oracle之分析函数解析及其应用场景
ORACLE 分析函数FIRST_VALUE,LAST_VALUE用法sum overavg over first_value overlast_value over...聚合函数结合over就是分析 ...
- 超级牛皮的oracle的分析函数over(Partition by...) 及开窗函数
over(Partition by...) 一个超级牛皮的ORACLE特有函数. 天天都用ORACLE,用了快2年了.最近才接触到这个功能强大而灵活的函数.真实惭愧啊! oracle的分析函数over ...
- oracle的分析函数over(Partition by...) 及开窗函数
over(Partition by...) 一个超级牛皮的ORACLE特有函数. oracle的分析函数over 及开窗函数一:分析函数overOracle从8.1.6开始提供分析函数,分析函 ...
- Mysql 分段统计
今天遇到个小问题觉得挺有意思,与大家分享. 需求是这样的,对数据库中的一张表做按时间的分段统计,结果只要每个区间的数量. select YEAR(create_time) as nian,MONTH( ...
- MySQL分段统计SQL写法 与 Mybatis 异常 java.math.BigDecimal cannot be cast to java.lang.Integer
mysql> select end) as '<60', end) as '60~69', end) as '70~79', end) as '80~89', end) as '>= ...
- MySQL高级函数case的使用技巧----与sum结合实现分段统计
case 函数 CASE WHEN condition1 THEN result1 WHEN condition2 THEN result2 ... WHEN conditionN THEN resu ...
随机推荐
- Jboss调优——最佳线程数
在设置jboss的参数中,maxThreads(最大线程数)和acceptCount(最大等待线程数)是两个非常重要的指标,直接影响到程序的QPS.本文讲解jboss连接的运行原理,以及如何设置这两 ...
- MYSQL语句中SELECT语句及其子句的执行顺序
SELECT语句的执行的逻辑查询处理步骤:(8)SELECT (9)DISTINCT(11)<TOP_specification> <select_list>(1)FROM & ...
- 数组工具类 - ArrayUtil.java
数组工具类,提供数组.对象之间转换的方法. 源码如下:(点击下载 - ArrayUtil.java .commons-lang-2.6.jar) import java.lang.reflect.Ar ...
- AngularJs-ui modal 传参数
最近开始学习 AnjularJs: 看了两天项目的代码开始动手完成项目中的功能,碰到些问题记录下备忘:方便以后再碰到这样疑惑的coder. 参见 Angular-ui modal 传递 header ...
- 在cmd中登录ftp服务器
http://jingyan.baidu.com/article/0bc808fc8778ee1bd485b93b.html C:\Users\Administrator>ftpftp> ...
- linux字符驱动程序结构
linux内核为字符设备的驱动程序设计,提供了一些数据结构,和函数,供开发人员调用,将设备驱动程序注册到内核去.现代操作系统几乎都不直接和硬件通信,而是通过定义的接口,是硬件厂商自己来开发符合标准某个 ...
- link cut tree 入门
鉴于最近写bzoj还有51nod都出现写不动的现象,决定学习一波厉害的算法/数据结构. link cut tree:研究popoqqq那个神ppt. bzoj1036:维护access操作就可以了. ...
- sed总结
[-] 简介 调用方式 选项 命令集合 寻址方式 基本用法 文件操作 附加-插入-修改文本 删除文本 替换文本 转换文本 补充 ------------------------------------ ...
- hihoCoder #1195 高斯消元·一
题意:便利店老板为了促销,推出了组合包的形式,将不同数量的各类商品打包成一个组合.比如2袋薯片,1听可乐的组合只要5元,而1袋薯片,2听可乐的组合只要4元.通过询问老板知道:一共有N种不同的商品和M种 ...
- 决定undo表空间的大小
1.查询每秒最高需要的undo的数据块[每个块8k大小] sys)) from v$undostat; )) --------------------------------------------- ...