缓慢变化维

缓慢变化维(SCD;Slowly Changing Dimensions)。在现实世界中,维度的属性随着时间的流失发生缓慢的变化(缓慢是相对事实表而言,事实表数据变化的速度比维度表快)。

处理维度表的历史变化信息的问题称为处理缓慢变化维的问题,简称SCD问题。处理缓慢变化维的方法有以下几种常见方式:

  • 保留原值

    维度属性值不做更改,保留原始值。

    如商品上架售卖时间:一个商品上架售卖后由于其他原因下架,后来又再次上架,此种情况产生了多个商品上架售卖时间。如果业务重点关注的是商品首次上架售卖时间,则采用该方式。

  • 直接覆盖

    修改维度属性为最新值,直接覆盖,不保留历史信息。

    如商品属于哪个品类:当商品品类发生变化时,直接重写为新品类。

  • 增加新属性列

    在维度表中增加新的一列,原先属性列存放上一版本的属性值,当前属性列存放当前版本的属性值,还可以增加一列记录变化的时间。

    缺点:只能记录最后一次变化的信息。


  • 快照表

    每天保留一份全量数据。

    简单、高效。缺点是信息重复,浪费磁盘空间。

    适用范围:维表不能太大

    使用场景多,范围广;一般而言维表都不大。

  • 拉链表

    拉链表适合于:表的数据量大,而且数据会发生新增和变化,但是大部分是不变的(数据发生变化的百分比不大),且是缓慢变化的(如电商中用户信息表中的某些用户基本属性不可能每天都变化)。主要目的是节省存储空间。

    适用场景:

    • 表的数据量大

    • 表中部分字段会被更新

    • 表中记录变量的比例不高

    • 需要保留历史信息

维表拉链表应用案例


创建表加载数据(准备工作)

  • 创建原始表与拉链表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    -- 用户信息
    DROP TABLE IF EXISTS test.userinfo;
    CREATE TABLE test.userinfo(
    userid STRING COMMENT '用户编号',
    mobile STRING COMMENT '手机号码',
    regdate STRING COMMENT '注册日期'
    ) COMMENT '用户信息'
    PARTITIONED BY (dt string)
    row format delimited fields terminated by ',';

    -- 拉链表(存放用户历史信息)
    -- 拉链表不是分区表;多了两个字段start_date、end_date
    DROP TABLE IF EXISTS test.userhis;
    CREATE TABLE test.userhis(
    userid STRING COMMENT '用户编号',
    mobile STRING COMMENT '手机号码',
    regdate STRING COMMENT '注册日期',
    start_date STRING,
    end_date STRING
    )
    COMMENT '用户信息拉链表'
    row format delimited fields terminated by ',';
  • 创建本地数据文件(/data/lagoudw/data/userinfo.dat)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    cd /data/lagoudw/data

    vim userinfo.dat

    001,13551111111,2020-03-01,2020-06-20
    002,13561111111,2020-04-01,2020-06-20
    003,13571111111,2020-05-01,2020-06-20
    004,13581111111,2020-06-01,2020-06-20
    002,13562222222,2020-04-01,2020-06-21
    004,13582222222,2020-06-01,2020-06-21
    005,13552222222,2020-06-21,2020-06-21
    004,13333333333,2020-06-01,2020-06-22
    005,13533333333,2020-06-21,2020-06-22
    006,13733333333,2020-06-22,2020-06-22
    001,13554444444,2020-03-01,2020-06-23
    003,13574444444,2020-05-01,2020-06-23
    005,13555554444,2020-06-21,2020-06-23
    007,18600744444,2020-06-23,2020-06-23
    008,18600844444,2020-06-23,2020-06-23
  • 静态分区数据加载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # 创建/data/lagoudw/data/userinfo0620.dat,内容如下
    001,13551111111,2020-03-01,2020-06-20
    002,13561111111,2020-04-01,2020-06-20
    003,13571111111,2020-05-01,2020-06-20
    004,13581111111,2020-06-01,2020-06-20
    # 静态分区数据加载,hive执行
    load data local inpath '/data/lagoudw/data/userinfo0620.dat' into table test.userinfo partition(dt='2020-06-20');

    # 创建/data/lagoudw/data/userinfo0621.dat,内容如下
    002,13562222222,2020-04-01,2020-06-21
    004,13582222222,2020-06-01,2020-06-21
    005,13552222222,2020-06-21,2020-06-21
    # 静态分区数据加载,hive执行
    load data local inpath '/data/lagoudw/data/userinfo0621.dat' into table test.userinfo partition(dt='2020-06-21');

    # 创建/data/lagoudw/data/userinfo0622.dat,内容如下
    004,13333333333,2020-06-01,2020-06-22
    005,13533333333,2020-06-21,2020-06-22
    006,13733333333,2020-06-22,2020-06-22
    # 静态分区数据加载,hive执行
    load data local inpath '/data/lagoudw/data/userinfo0622.dat' into table test.userinfo partition(dt='2020-06-22');

    # 创建/data/lagoudw/data/userinfo0623.dat,内容如下
    001,13554444444,2020-03-01,2020-06-23
    003,13574444444,2020-05-01,2020-06-23
    005,13555554444,2020-06-21,2020-06-23
    007,18600744444,2020-06-23,2020-06-23
    008,18600844444,2020-06-23,2020-06-23
    # 静态分区数据加载,hive执行
    load data local inpath '/data/lagoudw/data/userinfo0623.dat' into table test.userinfo partition(dt='2020-06-23');
  • 动态分区数据加载:分区的值是不固定的,由输入数据确定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    -- 创建中间表(非分区表)
    drop table if exists test.tmp1;
    create table test.tmp1 as select * from test.userinfo;

    -- tmp1 非分区表,使用系统默认的字段分割符'\001'
    alter table test.tmp1 set serdeproperties('field.delim'=',');

    -- 向中间表加载数据
    load data local inpath '/data/lagoudw/data/userinfo.dat' into table test.tmp1;

    -- 从中间表向分区表加载数据
    set hive.exec.dynamic.partition.mode=nonstrict;
    insert into table test.userinfo partition(dt)
    select * from test.tmp1;
  • 与动态分区相关的参数

    • hive.exec.dynamic.partition,表示开启动态分区功能

      • Default Value: false prior to Hive 0.9.0;

      • true in Hive 0.9.0 and later Added In: Hive 0.6.0

      Whether or not to allow dynamic partitions in DML/DDL.

    • hive.exec.dynamic.partition.mode

      • Default Value: strict

      • Added In: Hive 0.6.0

      In strict mode, the user must specify at least one static partition in case the user accidentally overwrites all partitions. In nonstrict mode all partitions are allowed to be dynamic.

      Set to nonstrict to support INSERT … VALUES, UPDATE, and DELETE transactions (Hive 0.14.0 and later).

      strict:最少需要有一个是静态分区

      nonstrict:可以全部是动态分区

    • hive.exec.max.dynamic.partitions

      • Default Value: 1000

      • Added In: Hive 0.6.0

      Maximum number of dynamic partitions allowed to be created in total.

      表示一个动态分区语句可以创建的最大动态分区个数,超出报错

    • hive.exec.max.dynamic.partitions.pernode

      • Default Value: 100

      • Added In: Hive 0.6.0

      Maximum number of dynamic partitions allowed to be created in each mapper/reducer node.

      表示每个mapper / reducer可以允许创建的最大动态分区个数,默认是100,超出则会报错。

    • hive.exec.max.created.files

      • Default Value: 100000

      • Added In: Hive 0.7.0

      Maximum number of HDFS files created by all mappers/reducers in a MapReduce job.

      表示一个MR job可以创建的最大文件个数,超出报错。

拉链表的实现

userinfo(分区表) => userid、mobile、regdate => 每日变更的数据(修改的+新增的) / 历史数据(第一天)

userhis(拉链表)=> 多了两个字段 start_date / end_date

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
-- 1、userinfo初始化(2020-06-20)。获取历史数据
001,13551111111,2020-03-01,2020-06-20
002,13561111111,2020-04-01,2020-06-20
003,13571111111,2020-05-01,2020-06-20
004,13581111111,2020-06-01,2020-06-20

-- 2、初始化拉链表(2020-06-20)。userinfo => userhis
insert overwrite table test.userhis
select userid, mobile, regdate, dt as start_date, '9999-12- 31' as end_date
from test.userinfo where dt='2020-06-20';

-- 3、次日新增数据(2020-06-21);获取新增数据
002,13562222222,2020-04-01,2020-06-21
004,13582222222,2020-06-01,2020-06-21
005,13552222222,2020-06-21,2020-06-21

-- 4、构建拉链表(userhis)(2020-06-21)【核心】 userinfo(2020-06- 21) + userhis => userhis
-- 4.1、处理新增数据【userinfo】(处理逻辑与加载历史数据类似)
select userid, mobile, regdate, dt as start_date, '9999-12- 31' as end_date
from test.userinfo where dt='2020-06-21';

-- 4.2、处理历史数据【userhis】(历史包括两部分:变化的、未变化的)
-- 变化的:start_date:不变;end_date:传入日期-1
-- 未变化的:不做处理
select B.userid, B.mobile, B.regdate, B.start_Date,
case when B.end_date='9999-12-31' and A.userid is not null
then date_add('2020-06-21', -1)
else B.end_date
end as end_date
from (select * from test.userinfo where dt='2020-06-21') A
right join test.userhis B on A.userid=B.userid;

-- 4.3、最终的处理(新增+历史数据)
insert overwrite table test.userhis select userid, mobile, regdate, dt as start_date, '9999-12- 31' as end_date
from test.userinfo where dt='2020-06-21'
union all
select B.userid, B.mobile, B.regdate, B.start_Date,
case when B.end_date='9999-12-31' and A.userid is not null
then date_add('2020-06-21', -1)
else B.end_date
end as end_date
from (select * from test.userinfo where dt='2020-06-21') A
right join test.userhis B on A.userid=B.userid;

-- 5、第三日新增数据(2020-06-22);获取新增数据
004,13333333333,2020-06-01,2020-06-22
005,13533333333,2020-06-21,2020-06-22
006,13733333333,2020-06-22,2020-06-22

-- 6、构建拉链表(2020-06-22) userinfo(2020-06-22) + userhis => userhis
insert overwrite table test.userhis select userid, mobile, regdate, dt as start_date, '9999-12- 31' as end_date
from test.userinfo where dt='2020-06-22'
union all
select B.userid, B.mobile, B.regdate, B.start_Date,
case when B.end_date='9999-12-31' and A.userid is not null
then date_add('2020-06-22', -1)
else B.end_date
end as end_date
from (select * from test.userinfo where dt='2020-06-22') A
right join test.userhis B on A.userid=B.userid;

-- 7、第四日新增数据(2020-06-23);获取新增数据
001,13554444444,2020-03-01,2020-06-23
003,13574444444,2020-05-01,2020-06-23
005,13555554444,2020-06-21,2020-06-23
007,18600744444,2020-06-23,2020-06-23
008,18600844444,2020-06-23,2020-06-23

-- 8、构建拉链表(2020-06-23) userinfo(2020-06-23) + userhis => userhis
insert overwrite table test.userhis select userid, mobile, regdate, dt as start_date, '9999-12- 31' as end_date
from test.userinfo where dt='2020-06-23'
union all
select B.userid, B.mobile, B.regdate, B.start_Date,
case when B.end_date='9999-12-31' and A.userid is not null
then date_add('2020-06-23', -1)
else B.end_date
end as end_date
from (select * from test.userinfo where dt='2020-06-23') A
right join test.userhis B on A.userid=B.userid;

处理拉链表的脚本(测试脚本):

创建/data/lagoudw/data/userzipper.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
source /etc/profile
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi

sql="
insert overwrite table test.userhis
select userid, mobile, regdate, dt as start_date, '9999-12- 31' as end_date
from test.userinfo
where dt='$do_date'
union all
select B.userid, B.mobile, B.regdate, B.start_Date,
case when B.end_date='9999-12-31' and A.userid is not null
then date_add('$do_date', -1)
else B.end_date
end as end_date
from (select * from test.userinfo where dt='$do_date') A
right join test.userhis B on A.userid=B.userid;
"

hive -e "$sql"

拉链表的使用:

1
2
3
4
5
6
7
8
9
10
11
-- 查看拉链表中最新数据(2020-06-23以后的数据)
select * from userhis where end_date='9999-12-31';

-- 查看拉链表中给定日期数据("2020-06-22")
select * from userhis where start_date <= '2020-06-22' and end_date >= '2020-06-22';

-- 查看拉链表中给定日期数据("2020-06-21")
select * from userhis where start_date <= '2020-06-21' and end_date >= '2020-06-21';

-- 查看拉链表中给定日期数据("2020-06-20")
select * from userhis where start_date <= '2020-06-20' and end_date >= '2020-06-20';

拉链表的回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
06-20拉链表数据(sh xxx.sh 2020-06-20;在2020-06-21日凌晨发出命令):
001 13551111111 2020-03-01 2020-06-20 9999-12-31
002 13561111111 2020-04-01 2020-06-20 9999-12-31
003 13571111111 2020-05-01 2020-06-20 9999-12-31
004 13581111111 2020-06-01 2020-06-20 9999-12-31

06-21拉链表数据(sh xxx.sh 2020-06-21):
001 13551111111 2020-03-01 2020-06-20 9999-12-31
002 13561111111 2020-04-01 2020-06-20 2020-06-20
002 13562222222 2020-04-01 2020-06-21 9999-12-31
003 13571111111 2020-05-01 2020-06-20 9999-12-31
004 13581111111 2020-06-01 2020-06-20 2020-06-20
004 13582222222 2020-06-01 2020-06-21 9999-12-31
005 13552222222 2020-06-21 2020-06-21 9999-12-31

06-22拉链表数据:
001 13551111111 2020-03-01 2020-06-20 9999-12-31
002 13561111111 2020-04-01 2020-06-20 2020-06-20
002 13562222222 2020-04-01 2020-06-21 9999-12-31
003 13571111111 2020-05-01 2020-06-20 9999-12-31
004 13581111111 2020-06-01 2020-06-20 2020-06-20
004 13582222222 2020-06-01 2020-06-21 2020-06-21
004 13333333333 2020-06-01 2020-06-22 9999-12-31
005 13552222222 2020-06-21 2020-06-21 2020-06-21
005 13533333333 2020-06-21 2020-06-22 9999-12-31
006 13733333333 2020-06-22 2020-06-22 9999-12-31

06-23拉链表数据:
001 13551111111 2020-03-01 2020-06-20 2020-06-22
001 13554444444 2020-03-01 2020-06-23 9999-12-31
002 13561111111 2020-04-01 2020-06-20 2020-06-20
002 13562222222 2020-04-01 2020-06-21 9999-12-31
003 13571111111 2020-05-01 2020-06-20 2020-06-22
003 13574444444 2020-05-01 2020-06-23 9999-12-31
004 13581111111 2020-06-01 2020-06-20 2020-06-20
004 13582222222 2020-06-01 2020-06-21 2020-06-21
004 13333333333 2020-06-01 2020-06-22 9999-12-31
005 13552222222 2020-06-21 2020-06-21 2020-06-21
005 13533333333 2020-06-21 2020-06-22 2020-06-22
005 13555554444 2020-06-21 2020-06-23 9999-12-31
006 13733333333 2020-06-22 2020-06-22 9999-12-31
007 18600744444 2020-06-23 2020-06-23 9999-12-31
008 18600844444 2020-06-23 2020-06-23 9999-12-31

由于种种原因需要将拉链表恢复到 rollback_date 那一天的数据。此时有:

  • end_date < rollback_date,即结束日期 < 回滚日期。表示该行数据在 rollback_date 之前产生,这些数据需要原样保留

  • start_date <= rollback_date <= end_date,即开始日期 <= 回滚日期 <= 结束日期。这些数据是回滚日期之后产生的,但是需要修改。将end_date 改为 9999-12-31

  • 其他数据不用管


  • 1、处理 end_date < rollback_date 的数据,保留

    1
    2
    3
    select userid, mobile, regdate, start_date, end_date, '1' as tag
    from test.userhis
    where end_date < '2020-06-22';
  • 2、处理 start_date <= rollback_date <= end_date 的数据,设置end_date=9999-12-31

    1
    2
    3
    select userid, mobile, regdate, start_date, '9999-12-31' as end_date, '2' as tag
    from test.userhis
    where start_date <= '2020-06-22' and end_date >= '2020-06- 22';
  • 3、将前面两步的数据写入临时表tmp(拉链表)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    drop table test.tmp;

    create table test.tmp as
    select userid, mobile, regdate, start_date, end_date, '1' as tag
    from test.userhis
    where end_date < '2020-06-22'
    union all
    select userid, mobile, regdate, start_date, '9999-12-31' as end_date, '2' as tag
    from test.userhis
    where start_date <= '2020-06-22' and end_date >= '2020-06- 22';

    select * from test.tmp cluster by userid, start_date;
  • 4、模拟脚本

    创建/data/lagoudw/data/zippertmp.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/bin/bash
    source /etc/profile
    if [ -n "$1" ] ;then
    do_date=$1
    else
    do_date=`date -d "-1 day" +%F`
    fi

    sql="
    drop table test.tmp;
    create table test.tmp as
    select userid, mobile, regdate, start_date, end_date, '1' as tag
    from test.userhis
    where end_date < '$do_date'
    union all
    select userid, mobile, regdate, start_date, '9999-12-31' as end_date, '2' as tag
    from test.userhis
    where start_date <= '$do_date' and end_date >= '$do_date';
    "

    hive -e "$sql"

    逐天回滚,检查数据;

方案二:保存一段时间的增量数据(userinfo),定期对拉链表做备份(如一个月做一次备份);如需回滚,直接在备份的拉链表上重跑增量数据。处理简单。

周期性事实表

有如下订单表,6月20号有3条记录(001/002/003):

订单创建日期 订单编号 订单状态
2020-06-20 001 创建订单
2020-06-20 002 创建订单
2020-06-20 003 支付完成

6月21日,表中有5条记录。其中新增2条记录(004/005),修改1条记录(001):

订单创建日期 订单编号 订单状态
2020-06-20 001 支付完成(从创建到支付)
2020-06-20 002 创建订单
2020-06-20 003 支付完成
2020-06-21 004 创建订单
2020-06-21 005 创建订单

6月22日,表中有6条记录。其中新增1条记录(006),修改2条记录(003/005):

订单创建日期 订单编号 订单状态
2020-06-20 001 支付完成
2020-06-20 002 创建订单
2020-06-20 003 已发货(从支付到发货)
2020-06-21 004 创建订单
2020-06-21 005 支付完成(从创建到支付)
2020-06-22 006 创建订单

订单事实表的处理方法:

  • 只保留一份全量。数据和6月22日的记录一样,如果需要查看6月21日订单001的状态,则无法满足;

  • 每天都保留一份全量。在数据仓库中可以在找到所有的历史信息,但数据量大了,而且很多信息都是重复的,会造成较大的存储浪费;

使用拉链表保存历史信息,会有下面这张表。历史拉链表,既能满足保存历史数据的需求,也能节省存储资源。

订单创建日期 订单编号 订单状态 begin_date end_date
2020-06-20 001 创建订单 2020-06-20 2020-06-20
2020-06-20 001 支付完成 2020-06-21 9999-12-31
2020-06-20 002 创建订单 2020-06-20 9999-12-31
2020-06-20 003 支付完成 2020-06-20 2020-06-21
2020-06-20 003 已发货 2020-06-22 9999-12-31
2020-06-21 004 创建订单 2020-06-21 9999-12-31
2020-06-21 005 创建订单 2020-06-21 2020-06-21
2020-06-21 005 支付完成 2020-06-22 9999-12-31
2020-06-22 006 创建订单 2020-06-22 9999-12-31

前提条件

  • 订单表的刷新频率为一天,当天获取前一天的增量数据;

  • 如果一个订单在一天内有多次状态变化,只记录最后一个状态的信息;

  • 订单状态包括三个:创建、支付、完成;

  • 创建时间和修改时间只取到天,如果源订单表中没有状态修改时间,那么抽取增量就比较麻烦,需要有个机制来确保能抽取到每天的增量数据;

数仓ODS层有订单表,数据按日分区,存放每天的增量数据:

1
2
3
4
5
6
7
8
9
DROP TABLE test.ods_orders;
CREATE TABLE test.ods_orders(
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING
)
PARTITIONED BY (dt STRING)
row format delimited fields terminated by ',';

数仓DWD层有订单拉链表,存放订单的历史状态数据:

1
2
3
4
5
6
7
8
9
10
DROP TABLE test.dwd_orders;
CREATE TABLE test.dwd_orders(
orderid INT,
createtime STRING,
modifiedtime STRING,
status STRING,
start_date STRING,
end_date STRING
)
row format delimited fields terminated by ',';

周期性事实表拉链表的实现

  • 1. 全量初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -- 数据文件order1.dat
    001,2020-06-20,2020-06-20,创建
    002,2020-06-20,2020-06-20,创建
    003,2020-06-20,2020-06-20,支付
    load data local inpath '/data/lagoudw/data/order1.dat' into table test.ods_orders partition(dt='2020-06-20');

    INSERT overwrite TABLE test.dwd_orders
    SELECT orderid, createtime, modifiedtime, status, createtime AS start_date, '9999-12-31' AS end_date
    FROM test.ods_orders
    WHERE dt='2020-06-20';
  • 2. 增量抽取

    1
    2
    3
    4
    5
    -- 数据文件order2.dat
    001,2020-06-20,2020-06-21,支付
    004,2020-06-21,2020-06-21,创建
    005,2020-06-21,2020-06-21,创建
    load data local inpath '/data/lagoudw/data/order2.dat' into table test.ods_orders partition(dt='2020-06-21');
  • 3. 增量刷新历史数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    -- 拉链表中的数据分两部实现:新增数据(ods_orders)、历史数据 (dwd_orders)
    -- 处理新增数据
    SELECT orderid, createtime, modifiedtime, status, modifiedtime AS start_date, '9999-12-31' AS end_date
    FROM test.ods_orders where dt='2020-06-21';

    -- 处理历史数据。历史数据包括:有修改、无修改的数据
    -- ods_orders 与 dwd_orders 进行表连接
    -- 连接上,说明数据被修改
    -- 未连接上,说明数据未被修改
    select A.orderid, A.createtime, A.modifiedtime, A.status, A.start_date,
    case when B.orderid is not null and A.end_date>'2020- 06-21'
    then '2020-06-20'
    else A.end_date
    end end_date
    from dwd_orders A
    left join (select * from ods_orders where dt='2020-06- 21') B on A.orderid=B.orderid;

    -- 用以上信息覆写拉链表
    insert overwrite table test.dwd_orders
    SELECT orderid, createtime, modifiedtime, status, modifiedtime AS start_date, '9999-12-31' AS end_date
    FROM test.ods_orders
    where dt='2020-06-21'
    union all
    select A.orderid, A.createtime, A.modifiedtime, A.status, A.start_date,
    case when B.orderid is not null and A.end_date>'2020- 06-21'
    then '2020-06-20'
    else A.end_date
    end end_date
    from dwd_orders A
    left join (select * from ods_orders where dt='2020-06- 21') B on A.orderid=B.orderid;

拉链表小结