巧用分析函数循序渐进解决实际问题 (r4笔记第10天)
今天同事问我一个问题,他说问题的逻辑很清晰,但是感觉无心下起。问题的逻辑大体是这样的。
有一个表,存在着大量的数据,比如account_id为1代表account的编号,可以把这个account做暂停操作,相当于把账户冻结,然后在一定的时候后做恢复操作,相当于把账户解冻。就对应ACTIVITY_CODE的 SUSPEND,RESUME
ACCOUNT_ID ACTIVITY_CODE EFFECTIVE_DATE
---------- ------------------------------ ------------------
1 SUSPEND 28-DEC-14
1 RESUME 01-JAN-15
1 SUSPEND 03-JAN-15
1 RESUME 06-JAN-15
1 SUSPEND 07-JAN-15
1 RESUME 08-JAN-15
现在想统计这些操作的累计时间,比如对于ACCOUNT_ID为1的账户来说,存在3组操作。
SUSPEND--RESUME 28-DEC-14 ~ 01-JAN-15 耗时4天
SUSPEND--RESUME 03-JAN-15 ~ 06-JAN-15 耗时3天
SUSPEND--RESUME 07-JAN-15 ~ 08-JAN-15 耗时1天
其中SUSPEND和RESUME操作是一组操作,RESUME操作依赖于SUSPEND,这一点很容易理解,要解冻账号,首先账号得已经冻结。
所以对于上面的操作来说我们期望得到对于ACCOUNT_ID为1的账户来说,耗时8天。
如果单纯得到最大值减去最小值得到的时间差是不准确的,毕竟每组操作的时间是不连续的。
我使用了如下的语句进行了简单模拟。
首先创建表test
create table test(account_id number,activity_code varchar2(30),effective_date date);
插入一些数据,为了尽量使数据看起来不是很规整,把数据的插入顺序进行了微调。
insert into test values(1,'SUSPEND',sysdate-12);
insert into test values(1,'RESUME',sysdate-8);
insert into test values(1,'SUSPEND',sysdate -6);
insert into test values(1,'RESUME',sysdate-3);
insert into test values(2,'SUSPEND',sysdate-10);
insert into test values(2,'RESUME',sysdate-7);
insert into test values(2,'SUSPEND',sysdate -5);
insert into test values(2,'RESUME',sysdate-4);
insert into test values(2,'SUSPEND',sysdate-2);
insert into test values(2,'RESUME',sysdate-1);
insert into test values(1,'SUSPEND',sysdate-2);
insert into test values(1,'RESUME',sysdate-1);
这样我们得到了一个基本的列表。代表某个账户在某个时间段进行了某些操作。
ACCOUNT_ID ACTIVITY_CODE EFFECTIVE_DATE
---------- ------------------------------ ------------------
1 SUSPEND 28-DEC-14
1 RESUME 01-JAN-15
1 SUSPEND 03-JAN-15
1 RESUME 06-JAN-15
2 SUSPEND 30-DEC-14
2 RESUME 02-JAN-15
2 SUSPEND 04-JAN-15
2 RESUME 05-JAN-15
2 SUSPEND 07-JAN-15
2 RESUME 08-JAN-15
1 SUSPEND 07-JAN-15
1 RESUME 08-JAN-15
这个时候我们对数据进行基本的排序,就能够清晰的看到这些操作的时间。
select *from test order by account_id,effective_date;
ACCOUNT_ID ACTIVITY_CODE EFFECTIVE_DATE
---------- ------------------------------ ------------------
1 SUSPEND 28-DEC-14
1 RESUME 01-JAN-15
1 SUSPEND 03-JAN-15
1 RESUME 06-JAN-15
1 SUSPEND 07-JAN-15
1 RESUME 08-JAN-15
2 SUSPEND 30-DEC-14
2 RESUME 02-JAN-15
2 SUSPEND 04-JAN-15
2 RESUME 05-JAN-15
2 SUSPEND 07-JAN-15
2 RESUME 08-JAN-15
我们进行重要的一步操作,把操作进行分组,即SUSPEND-RESUME的操作合成一些,可以使用分析函数lead来完成。 比如对于
select t.account_id,effective_date,lead(effective_date,1,effective_date) over(partition by account_id order by account_id,effective_date) next_date from test t
2 /
ACCOUNT_ID EFFECTIVE_DATE NEXT_DATE
---------- ------------------ ------------------
1 28-DEC-14 01-JAN-15
1 01-JAN-15 03-JAN-15
1 03-JAN-15 06-JAN-15
1 06-JAN-15 07-JAN-15
1 07-JAN-15 08-JAN-15
1 08-JAN-15 08-JAN-15
2 30-DEC-14 02-JAN-15
2 02-JAN-15 04-JAN-15
2 04-JAN-15 05-JAN-15
2 05-JAN-15 07-JAN-15
2 07-JAN-15 08-JAN-15
2 08-JAN-15 08-JAN-15
这样就把操作进行了一个初步的分组,但是标黄列的数据是错误的,它统计的是RESUME-SUSPEND的时间差,这个和逻辑不符,我们需要得到的是SUSPEND-RESUME的时间差。我们可以进行简单过滤。
SQL> select rownum,mod(rownum,2)chk_num,t.account_id,next_date-effective_date duration from
2 (
3 select t.account_id,effective_date,lead(effective_date,1,effective_date) over(partition by account_id order by account_id,effective_date) next_date from test t
4 )t
5 /
ROWNUM CHK_NUM ACCOUNT_ID DURATION
---------- ---------- ---------- ----------
1 1 1 4
2 0 1 2
3 1 1 3
4 0 1 1
5 1 1 1
6 0 1 0
7 1 2 3
8 0 2 2
9 1 2 1
10 0 2 2
11 1 2 1
12 0 2 0
对于mod(ronum,2)=0的数据行来说,这些数据可以排除,这些时间差是不需要考虑的。
最后的完整sql如下:
select t.account_id,sum(duration)
from
(
select rownum,mod(rownum,2)chk_num,t.account_id,next_date-effective_date duration from
(
select t.account_id,effective_date,lead(effective_date,1,effective_date) over(partition by account_id order by account_id,effective_date) next_date from test t
)t
)t where chk_num=1
group by account_id;
ACCOUNT_ID SUM(DURATION)
---------- -------------
1 8
2 5
可以看到account_id为1的账户耗时8天,account_id为2的账户耗时5天。
在表中含有大量的数据前提下,这个操作的资源消耗也不高。如果采用pl/sql也可以解决,不过可能代码要更多。
- 无限级分类(非递归算法/存储过程版/GUID主键)完整数据库示例_(4)显示记录
- Android 2.x中使用actionbar - Actionbarsherlock (2)
- python读txt和xml
- 让Jexus支持高并发请求的优化技巧
- 数据压缩算法LZO (C#)
- Html之初体验
- 基于Wolfpack开发业务监控系统
- Android 2.x中使用actionbar - Actionbarsherlock
- Python-操作Memcache、Redis、RabbitMQ、
- 推荐[搜索引擎架构]的几篇文章
- 中小型商城系统中的分类/产品属性/扩展属性的数据库设计
- Linux下FTP虚拟账号环境部署总结
- Replace方法与正则表达式的性能比较
- 由索引节点(inode)爆满引发的问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- flask SQLAlchemy查询数据库最近30天,一个月,一周,12小时或之前的数据
- TypeScript是什么,为什么要使用它?
- Python 基础(十):模块与包
- 谷歌浏览器一些https打不开点击高级不行的解决办法
- CV入门赛最全思路&上分技巧汇总!
- VBA专题10-5:使用VBA操控Excel界面之隐藏/取消隐藏及最小化功能区
- 从一道面试题说起:GET 请求能传图片吗?
- ModuleNotFoundError: No module named ‘pip‘【已解决】
- 【原创】Java并发编程系列31 | 阻塞队列(上)
- 【2019-3-3】Mac启动redis
- awvs(acunetix)使用一段时间后突然不能用了-解决方案
- Java自动化测试(数据库断言 18)
- Java自动化测试(参数化 19)
- Python 基础(一):入门必备知识
- Mac安装软件提示 已损坏【已解决】