使用的是ORACLE数据库实例,实例名是CHENMU,用户名和密码是scott/tiger

一、应用场景

第一种:前面一列是后面一列的初始节点,不允许时间穿插,比如C节点时间在A、B节点中间,C所在时间点有节点就不会 填充了,会导致失误。使用分析函数、first_value等实现缺失日期的填充,这种适合缺失的日期全部填充,且时间严格从小到大(或从大到小),没有时间穿插。

第二种:做好节点的前后节点维表,可以允许时间穿插,比如C在A、B节点中间,C所在时间点有节点还是填充(有C节点也填充A节点)。手工维护前节点表,前节点与节点名称关联得到两个节点时间,再填充时间范围(不用between,用>、<,这样两个节点之间的时间就填充为前节点,临界点不填充,如果使用explode会填充临界点,explode是hive里面的用法),再和原来的数据集合并。

二、第一种场景

1、数据集准备 (以一个月为单位),在没有出现日期的节点自动填充成前面第一个节点日期存在的值。

使用分析函数sum() over(partition by ... order by...)和first_value over(partition by ... order by ...)。先准备两个数据集,一是全部的日期维表,即包含所有年月的数据集;二是待处理的数据集,通过SQL*Loader导入数据库。

(1)、日期数据集 (外部文件/tmp/date_test.txt):范围是2020-2023,后面的值太长了省略掉( 如果有hive数据库,只需获取代处理的数据集里面的最小时间和最大时间,通过sequence(最小时间,最大时间,interval 1 month)将两个时间的所有间隔时间处理成一个时间数据组,再通过explode将这个数组分成多行,就是如下的数据集 ),日期维表。

"SMONTH"
"2020-02-01"
"2020-01-01"
"2020-03-01"
"2020-04-01"
"2020-05-01"
"2020-06-01"
........

创建表DATE_TEST

create table date_test(SMONTH varchar2(20));

控制文件内容

vi /tmp/date.ctl;
load data
infile '/tmp/date_test.txt'
into table date_test
fields terminated by ','
OPTIONALLY ENCLOSED BY '"'
(SMONTH)
$sqlldr scott/tiger control=/tmp/date.ctl skip=1;

(2)、待处理的数据集 (外部文件是/tmp/node_test.txt),cate是种类,A、B、C是各个节点

"cate","A","B","C"
"目标","2022-02-15","2022-05-24","2022-09-03"
"实际","2021-07-03","2021-12-11","2022-02-03"

创建表NODE_TEST

create table NODE_TEST
CATE VARCHAR2(20),
A VARCHAR2(20),
B VARCHAR2(20),
C VARCHAR2(20));

控制文件内容

vi /tmp/node.ctl;
load data
infile '/tmp/node_test.txt'
into table node_test
fields terminated by ','
OPTIONALLY ENCLOSED BY '"'
(CATE,A,B,C)
$sqlldr scott/tiger control=/tmp/node.ctl skip=1;

发现中文输出是乱码,需要修改字符集,之后再执行导入数据命令

truncate table node_test;
exit;
export NLS_LANG=AMERICAN_AMERICA.UTF8;
sqlldr scott/tiger control=/tmp/node.ctl skip=1;
sqlplus / as sysdba;
select * from scott.node_test;

2、填充日期

(1)、节点数据集列转行,并将日期置为当月第一天

conn scott/tiger;
alter session set nls_date_format='yyyy-mm-dd'; //调整日期格式 create table t0 as select cate,node,trunc(to_date(sdate,'yyyy-mm-dd'),'mm') sdate from node_test unpivot (sdate for node in(A,B,C));

(2)、填充缺失的日期,并关联节点(通过不等值连接得到目标和实际的时间范围并填充,之后与t0关联得节点信息)

create table t1 as
select t.cate,t.smonth,t0.node from 
(select cate,smonth from date_test,(select cate,min(sdate) min_date,max(sdate) max_date from t0 group by cate)
 where smonth between min_date and max_date
left join t0 on t.cate=t0.cate and t.smonth=t0.sdate
order by t.cate,t.smonth;

3、填充节点 (填充成和上一个非空节点相同)

(1)、计算节点数值 (空值置为0,非空值置为1,累加,结果后面节点为空的点数值和前面非空节点数值都一样)

create table t2 as 
select cate,smonth,node,sum(case when node is null then 0 else 1 end) over(partition by cate order by smonth,node) num
from t1;

(2)、将空值节点置为第一个非空值 (使用first_value函数)

select cate,smonth,node,num,first_value(node) over(partition by cate,num order by smonth)
from t2;

这样就得到最后的结果了。

三、第二种方法

在第一步已经准备数据集了,这里就不设置了。因为要测试C节点时间在A、B之间,但是C节点所在时间也要填充(该时间点不只存在C节点也要存在A节点),所以将C节点的时间修改一下。

1、修改目标和实际的C节点的时间

update  node_test set C='2022-04-13' where cate='目标';
update  node_test set C='2021-09-13' where cate='实际';

2、形成维表数据

create table dim
(node varchar(1),
Qnode varchar(1));
insert into dim values('A',null);
insert into dim values('B','A');
insert into dim values('C',null);

3、节点数据集列转行,并将日期置为当月第一天(和上面的第二步相同)

create table d0 as 
select cate,node,trunc(to_date(sdate,'yyyy-mm-dd'),'mm') sdate  from node_test
unpivot
(sdate for node in(A,B,C));

4、前节点时间与节点相关联获得两个节点之间的时间

create table d1 as
select
r1.*,dim.qnode,r2.sdate qdate from d0 r1 left join dim on r1.node=dim.node left join d0 r2 on r1.cate=r2.cate and dim.qnode=r2.node; //从维表中获取节点的前一节点,并关联自身获取前一节点的时间
select cate,node,sdate from d1
union all
select cate,qnode,to_date(smonth,'yyyy-mm-dd') from d1,date_test
where smonth>qdate and smonth <sdate
order by cate,sdate,node

得到结果。