Oracle数仓中判断时间连续性的几种SQL写法示例

零、需求介绍

现有一张表数据如下:

此表是一张镜像表,policyno列代表一个保单号,state列代表这个保单号在snapdate当天的最后一次状态(state每天可能会变很多次,镜像表只保留snapdate时间点凌晨的最后一次状态),snapdate代表当天做镜像的时间,现在有个需求,我们想取出来这个保单号连续保持某个状态的起止时间,例如:

保单号sm1保持状态1的起止时间为2021020120210202,然后在20210203时候变成了状态2,又在20210204时候变成了状态3,最终又在2021020520210209时间段保持在状态1,然后镜像表的程序可能期间出现过问题,在20210210开始到20210215日没有镜像成功,直到20210216日才恢复,20210216~20210219日保单号sm1的状态一直保持为1,后续还有可能继续变,那么,上面说的保单sm1的几个状态的连续时间,我们想要的结果为:

POLICYNO	STATE	START_DATE	END_DATE
sm1		1	20210201	20210202
sm1		2	20210203	20210203
sm1		3	20210204	20210204
sm1		1	20210205	20210209
sm1     1      20210216       20210219
.........................

我这里提供5种写法,可以归结为两大类:

一类:通过使用分析函数或自关联获取数据连续性,构造一个分组字段进行分组求最大最小值。

二类:通过树形层次查询获取连续性,获取起止时间。

一、通过使用lag分析函数获取前后时间,根据当前时间与前后时间的差值进行判断获取时间连续性标志,然后使用sum()over()对连续性标志进行累加,从而生成一个新的临时分组字段,最终根据policyno,state,临时分组字段进行分组取最大最小值

这里为了好理解,每一个处理步骤都单独写出来了,实际使用中可以简写一下:

with t as--求出来每条数据当天的前一天镜像时间
 (select a.policyno,
         a.state,
         a.snapdate,
         lag(a.snapdate) over(partition by a.policyno, a.state order by a.snapdate) as lag_tim
    from zyd.temp_0430 a
   order by a.policyno, a.snapdate),
t1 as--判断当天镜像时间和前一天的镜像时间+1是否相等,如果相等就置为0否则置为1,新增临时字段lxzt意为:连续状态标志
 (select t.*,
         case
           when t.snapdate = t.lag_tim + 1 then
            0
           else
            1
         end as lxzt
    from t
   order by policyno, snapdate),
t2 as--根据lxzt字段进行sum()over()求和,求出来一个新的用来做分组依据的字段,简称fzyj
 (select t1.*, sum(lxzt) over(order by policyno, snapdate) as fzyj from t1)
select policyno,--最后根据policyno,state,fzyj进行分组求最大最小值即为状态连续的开始结束时间
       state,
       -- fzyj,
       min(snapdate) as start_snap,
       max(snapdate) as end_snap
  from t2
 group by policyno, state, fzyj
 order by fzyj;

二、不使用lag分析函数,通过自关联也能判断出来哪些天连续,然后后面操作步骤同上,这个写法算是对lag()over()函数的一个回写,摆脱对分析函数的依赖

下面这种写法,需要读两次表,上面lag的方式是对这个写法的一种优化:

with t as
 (select a.policyno, a.state, a.snapdate, b.snapdate as snap2
    from zyd.temp_0430 a, zyd.temp_0430 b
   where a.policyno = b.policyno(+)
     and a.state = b.state(+)
     and a.snapdate - 1 = b.snapdate(+)
   order by policyno, snapdate),
t1 as
 (select t.*,
         case
           when snap2 is null then
            1
           else
            0
         end as lxzt
    from t
   order by policyno, snapdate),
t2 as
 (select t1.*, sum(lxzt) over(order by policyno, snapdate) as fzyj
    from t1
   order by policyno, snapdate)
select policyno,
       state,
       fzyj,
       min(snapdate) as start_snap,
       max(snapdate) as end_snap
  from t2
 group by policyno, state, fzyj
 order by fzyj;

三、通过构造树形结构,确定根节点和叶子节点来获取状态连续的开始和结束时间

先按照数据的连续性构造显示每层关系的树状结构:

with t as
 (select a.policyno,
         a.state,
         a.snapdate,
         lag(a.snapdate) over(partition by a.policyno, a.state order by a.snapdate) as lag_tim
    from zyd.temp_0430 a --where policyno='sm1'
   order by a.policyno, a.snapdate),
t1 as
 (select t.*,
         case
           when t.snapdate = t.lag_tim + 1 then
            0
           else
            1
         end as lxzt
    from t
   order by policyno, snapdate),
t2 as
 (select t1.*,
         lpad('->', (level - 1) * 2, '->') || snapdate as 树状结构,
         level as 树中层次,
         decode(level, 1, 1) 是否根节点,
         decode(connect_by_isleaf, 1, 1) 是否叶子节点,
         case
           when (connect_by_isleaf = 0 and level > 1) then
            1
         end  是否树杈,
         (prior snapdate) as 根值,
         connect_by_root snapdate 主根值
    from t1
   start with (lxzt = 1)
  connect by (prior snapdate = snapdate - 1
          and prior state = state and
              prior policyno = policyno)
   order by policyno, snapdate)
select * from t2;

从上面能清晰的看出来,每一次连续状态的开始日期作为每个树的根,分支节点即树杈和叶子节点的关系一步步拓展开来,分析上面数据我们能够知道,如果我们想要获取每个保单状态连续时间范围,以上面的数据现有分布方式,现在就可以:通过policyno,state,主根值进行group by 取snapdate的最大最小值,类似前面两个写法的最终步骤;

接下来,我们这个第三种写法就是按照这个方式写:

with t as
 (select a.policyno,
         a.state,
         a.snapdate,
         lag(a.snapdate) over(partition by a.policyno, a.state order by a.snapdate) as lag_tim
    from zyd.temp_0430 a --where policyno='sm1'
   order by a.policyno, a.snapdate),
t1 as
 (select t.*,
         case
           when t.snapdate = t.lag_tim + 1 then
            0
           else
            1
         end as lxzt
    from t
   order by policyno, snapdate),
t2 as
 (select t1.*,
         lpad('->', (level - 1) * 2, '->') || snapdate as 树状结构,
         level as 树中层次,
         decode(level, 1, 1) 是否根节点,
         decode(connect_by_isleaf, 1, 1) 是否叶子节点,
         case
           when (connect_by_isleaf = 0 and level > 1) then
            1
         end  是否树杈,
         (prior snapdate) as 根值,
         connect_by_root snapdate 主根值
    from t1
   start with (lxzt = 1)
  connect by (prior snapdate = snapdate - 1
          and prior state = state and
              prior policyno = policyno)
   order by policyno, snapdate)
select policyno,
       state,
       min(snapdate) as start_date,
       max(snapdate) as end_date
  from t2
 group by policyno, state, 主根值
 order by policyno, state;

四、参照过程三,既然已经获取了每条数据的主根值和叶子节点的值,这就代表了我们知道了每个保单状态的连续开始和结束时间,那直接取出来叶子节点数据,叶子节点主根值就是开始日期,叶子节点的值就是结束日期,这样我们就不需再group by了

with t as
 (select a.policyno,
         a.state,
         a.snapdate,
         lag(a.snapdate) over(partition by a.policyno, a.state order by a.snapdate) as lag_tim
    from zyd.temp_0430 a --where policyno='sm1'
   order by a.policyno, a.snapdate),
t1 as
 (select t.*,
         case
           when t.snapdate = t.lag_tim + 1 then
            0
           else
            1
         end as lxzt
    from t
   order by policyno, snapdate),
t2 as
 (select t1.*,
         lpad('->', (level - 1) * 2, '->') || snapdate as 树状结构,
         level as 树中层次,
         decode(level, 1, 1) 是否根节点,
         decode(connect_by_isleaf, 1, 1) 是否叶子节点,
         case
           when (connect_by_isleaf = 0 and level > 1) then
            1
         end 是否树杈,
         (prior snapdate) as 根值,
         connect_by_root snapdate 主根值
    from t1
   start with (lxzt = 1)
  connect by (prior snapdate = snapdate - 1 and prior state = state and
             prior policyno = policyno)
   order by policyno, snapdate)
select policyno, state, 主根值 as start_date, snapdate as end_date
  from t2
 where 是否叶子节点 = 1
 order by policyno, snapdate

五、在Oracle10g之前,上面树状查询的关键函数 connect_by_root还不支持,如果使用树形结构,可以通过sys_connect_by_path来实现

with t as
 (select a.policyno,
         a.state,
         a.snapdate,
         lag(a.snapdate) over(partition by a.policyno, a.state order by a.snapdate) as lag_tim
  --case when lag(a.snapdate) over(partition by a.policyno, a.state order by a.snapdate) is null then snapdate else lag(a.snapdate) over(partition by a.policyno, a.state order by a.snapdate) end as lag_tim
    from zyd.temp_0430 a
   order by a.policyno, a.snapdate),
t1 as
 (select t.*,
         case
           when t.snapdate = t.lag_tim + 1 then
            0
           else
            1
         end as lxzt
    from t
   order by policyno, snapdate),
t2 as
 (select t1.*,
         sys_connect_by_path(snapdate, ',') as pt,
         level,
         connect_by_isleaf as cb
    from t1
   start with (lxzt = 1)
  connect by (prior snapdate = snapdate - 1 and prior state = state and
             prior policyno = policyno))
select t2.*,
       regexp_substr(pt, '[^,]+', 1, 1) as start_date,
       regexp_substr(pt, '[^,]+', 1, regexp_count(pt, ',')) as end_date
  from t2
 where cb = 1
 order by policyno, state;

还有好多其他写法,这里不再一一列举!

总结

到此这篇关于Oracle数仓中判断时间连续性的几种SQL写法的文章就介绍到这了,更多相关Oracle数仓判断时间连续性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Oracle数仓中判断时间连续性的几种SQL写法示例

    零.需求介绍 现有一张表数据如下: 此表是一张镜像表,policyno列代表一个保单号,state列代表这个保单号在snapdate当天的最后一次状态(state每天可能会变很多次,镜像表只保留snapdate时间点凌晨的最后一次状态),snapdate代表当天做镜像的时间,现在有个需求,我们想取出来这个保单号连续保持某个状态的起止时间,例如: 保单号sm1保持状态1的起止时间为2021020120210202,然后在20210203时候变成了状态2,又在20210204时候变成了状态3,最终又

  • JavaScript中判断对象类型的几种方法总结

    我们知道,JavaScript中检测对象类型的运算符有:typeof.instanceof,还有对象的constructor属性: 1) typeof 运算符 typeof 是一元运算符,返回结果是一个说明运算数类型的字符串.如:"number","string","boolean","object","function","undefined"(可用于判断变量是否存在). 但 type

  • 在JavaScript中判断整型的N种方法示例介绍

    整数类型(Integer)在JavaScript经常会导致一些奇怪的问题.在ECMAScript的规范中,他们只存在于概念中: 所有的数字都是浮点数,并且整数只是没有一组没有小数的数字. 在这篇博客中,我会解释如何去检查某个值是否为整型. ECMAScript 5 在ES5中有很多方法你可以使用.有时侯,你可能想用自己的方法:一个isInteger(x)的函数,如果是整型返回true,否则返回false. 让我们看看一些例子. 通过余数检查 你可以使用余数运算(%),将一个数字按1求余,看看余数

  • Delphi中对时间操作方法汇总

    一般来说在delphi中用于描述时间的有几种数据结构,而对时间的操作,实质上就是对这些结构的操作. TDateTime类型: Delphi中最常用的表示日期时间的数据类型TDateTime类型,和普通的整形数一样,你可以给日期定义一个日期型变量以便在程序中进行操作.TdateTime类型实质上是一个Double型的数,在Delphi中是这样定义TdateTime类型: type TDateTime = type Double ,具体的算法是用Double数的整数部分表示日期,以1989年12月3

  • Oracle如何在SQL语句中对时间操作、运算

    目录 0.date与timestamp 1.获取系统当前时间 2.ORACLE里获取一个时间的年.季.月.周.日的函数: 3.日期操作 4.常用的时间戳 5.查询某时间范围 总结 0.date与timestamp 1)区别 date精确到年月日时分秒,timestamp更精确一些: 但这个不重要,重要的是,实践中我从Oracle数据库取date类型字段,前端展示时分秒都是0,网上说数据库类型是date取到前端就是这样,只能精确到日,后面都是默认填0:我给字段换成timestamp确实问题解决了,

  • 浅谈java中六大时间类的使用和区别

    java.util.Date java.sql.Date java.sql.Time java.sql.Timestamp java.text.SimpleDateFormat java.util.Calendar java.util.Date日期格式为:年月日时分秒 java.sql.Date日期格式为:年月日 java.sql.Time日期格式为:时分秒 java.sql.Timestamp日期格式为:年月日时分秒纳秒(毫微秒) 从数据库中取出来的日期一般都用getTimestamp()方法

  • Shell中关于时间和日期的函数总结

    shell下获取系统时间的方法直接调用系统变量 获取今天时期:`date +%Y%m%d` 或 `date +%F` 或 $(date +%y%m%d) 获取昨天时期:`date -d yesterday +%Y%m%d` 获取前天日期:`date -d -2day +%Y%m%d` 依次类推比如获取10天前的日期:`date -d -10day +%Y%m%d` 或n天前的 `date -d "n days ago" +%y%m%d` 明天:`date -d tomorrow +%y

  • C++11中的时间库std::chrono(引发关于时间的思考)

    前言 时间是宝贵的,我们无时无刻不在和时间打交道,这个任务明天下班前截止,你点的外卖还有5分钟才能送到,那个程序已经运行了整整48个小时,既然时间和我们联系这么紧密,我们总要定义一些术语来描述它,像前面说到的明天下班前.5分钟.48个小时都是对时间的描述,程序代码构建的程序世界也需要定义一些术语来描述时间. 今天要总结学习的是 std::chrono 库,它是 C++11 标准时从 boost 库中引入的,其实在 C++ 中还有一种 C 语言风格的时间管理体系,像我们常见的函数 time().c

  • SpringBoot中时间格式化的五种方法汇总

    目录 前言 时间问题演示 1.前端时间格式化 JS 版时间格式化 2.SimpleDateFormat格式化 3.DateTimeFormatter格式化 4.全局时间格式化 实现原理分析 5.部分时间格式化 总结 参考 & 鸣谢 前言 在我们日常工作中,时间格式化是一件经常遇到的事儿,所以本文我们就来盘点一下 Spring Boot 中时间格式化的几种方法. 时间问题演示 为了方便演示,我写了一个简单 Spring Boot 项目,其中数据库中包含了一张 userinfo 表,它的组成结构和数

  • Java中常用时间的一些相关方法

    目录 前言 一.获取当前时间的方式 二.获取当月第n天 三.格式化为字符串 四.加减时间(单位可以是秒,小时等) 五.通过出生日期获取年龄 六.判断两个时间段是否覆盖 七.求两个时间间隔 八.UTC时间与北京时间转换 总结 前言 在我们java开发中,Date日期这个字段会被经常使用,比如获取当前系统的时间,获取上个月,上一年的时间,以及获取两个日期相差的时分秒数,或者对日期类型进行格式化,等等,等等,下面将给大家详细介绍下Java中常用时间的一些相关方法 一.获取当前时间的方式 public

随机推荐