前言
1、DWD 层开发
DWD层设计要点:
(1)DWD层的设计依据是维度建模理论(主体是事务型事实表(选择业务过程 -> 声明粒度 -> 确定维度 -> 确定事实),另外两种周期型快照事实表和累积型事务事实表按需求选择),该层存储维度模型的事实表。
(2)DWD层的数据存储格式为orc列式存储+snappy压缩(和DIM层、DWS层都是一样的)。
(3)DWD层表名的命名规范为dwd_数据域_表名_单分区增量全量标识(inc/full)(划分数据域的目的是为了通过对数据分类使得从业务系统中可以快速的找到我们希望得到的数据;划分数据域的标准是按照业务过程,将若干个业务过程划分到一个数据域里面,其实所谓的划分数据域就是在划分事实表,因为一个业务过程对应一个事实表)。
1.1、交易域加购事务事实表
加购指的是加入购物车这个业务过程,我们按照设计事务事实表的过程来设计它的表结构:
1.1.1、选择业务过程
就是加购物车这个行为
1.1.2、声明粒度
要求尽可能选择最细粒度,声明粒度将来指代的是将来这张表的每一行所代表的内容。这里我们声明的粒度就是:谁+在什么时候+把什么商品加到了购物车
1.1.3、确认维度
我们首先能想到的就是用户、时间和商品,至于其它维度我们在设计事实表的时候尽可能多的考虑到,避免后期一些指标无法分析。
确认了维度,我们就确认了我们这张事务事实表的维度外键。
1.1.4、确认事实
确认事实就是确认事实表的度量值,对于这里的加购操作度量值主要就是加购的商品件数。
1.1.5、建表语句
这里我们的表名由几部分组成:dwd 代表这张表是 dwd 层的事实表;trade 代表数据域是交易域;cart_add 代表这张事实表的业务过程是加购操作;inc 因为这张表是事务型事实表所以我们一般都是增量表。
再看字段:其中 id 选取的是我们业务系统中 cart_info 的 id;user_id sku_id date_id 是我们的维度外键;sku_num 是我们的度量值。剩下的 create_time 是具体的加购时间,精确到秒,它区别于维度外键 date_id,date_id 是日期,只能精确到哪一天;source_id source_type_code source_type_name 这些字段都是来自我们业务系统的,我们之前说 DIM 层和 DWD 层的维度表和事实表都是业务驱动的,所以它们各自表的设计字段的选择来源于我们的业务系统中表,这里的加购事务事实表就是对应我们业务系统当中的 cart_info:
通过查询字典表可以看到:
不同的 source_type_code 代表不同类型的加购操作(用户通过哪种来源类型来加购的,比如用户通过广告来加购的,那通过哪个广告呢,所以这里我们还指定了 source_id),这些字段都算是维度,但是我们并没有对这些属性做一个维度表,而是直接放到事实表里(也就是维度退化,而维度退化要求不能只有编码,一般都是编码和文字描述共存的;所以这里我们保留了来源类型编码和来源类型名称)。
DROP TABLE IF EXISTS dwd_trade_cart_add_inc;
CREATE EXTERNAL TABLE dwd_trade_cart_add_inc
(`id` STRING COMMENT '编号',`user_id` STRING COMMENT '用户id',`sku_id` STRING COMMENT '商品id',`date_id` STRING COMMENT '时间id',`create_time` STRING COMMENT '加购时间',`source_id` STRING COMMENT '来源类型ID',`source_type_code` STRING COMMENT '来源类型编码',`source_type_name` STRING COMMENT '来源类型名称',`sku_num` BIGINT COMMENT '加购物车件数'
) COMMENT '交易域加购物车事务事实表'PARTITIONED BY (`dt` STRING)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_trade_cart_add_inc/'TBLPROPERTIES ('orc.compress' = 'snappy');
1.1.6、数据流向
现在我们表设计好了下一步就是数据源从哪来(从 ods 层的哪些表来),以及我们怎么向这张表进行装载(insert + select),这就需要我们了解一下这张表的不同维度的数据应该来自于哪些表。
我们首先需要了解业务系统中 order_info 这张表:
其实,对于加购这个过程无非只有两种情况:1、原本购物车没有这个商品然后进行加购,这是对数据库来说是一个 insert 操作;2、原本购物车就有这个商品然后加购,这对数据库来说是一个 update 操作。显式的变化就是 sku_num 的值的变化。
这样我们就搞清楚了加购这个业务过程,会对 order_info(或者可以说是 ods_order_info_inc) 产生影响,产生的影响就是 sku_num 字段的值发生变化。
现在我们不仅要知道数据从张表来,还得知道数据从具体哪个分区流向哪个分区:
首日数据:
所有的增量表都要在首日做一个全量同步,这里我们的加购表当前存储的就是首日的全量数据,我们需要对字段 create_time 做一个动态分区来把不同时间的加购信息放到不同的分区:
每日数据:
解决了首日全量数据的分区问题,我们之后 ods 层每天的分区中存储的就是 Maxwell 监听的每日的增量数据,所以这些都是加购信息,我们直接存储当当日分区即可:
1.1.7、首日装载语句
-- 动态分区需要设置非严格模式
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table dwd_trade_cart_add_inc partition (dt)
selectid,user_id,sku_id,date_format(create_time,'yyyy-MM-dd') date_id,create_time,source_id,source_type,dic.dic_name,sku_num,date_format(create_time, 'yyyy-MM-dd')
from
(selectdata.id,data.user_id,data.sku_id,data.create_time,data.source_id,data.source_type,data.sku_numfrom ods_cart_info_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
)ci
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code='24'
)dic
on ci.source_type=dic.dic_code;
1.1.8、每日装载语句
我们比如要装载 06-15 这一天的数据,首先我们需要保证 type 为 insert 或者 update 类型的操作,并且加购后的 sku_num 的值要大于旧的 sku_num 值。
insert overwrite table dwd_trade_cart_add_inc partition(dt='2020-06-15')
selectid,user_id,sku_id,date_id,create_time,source_id,source_type_code,source_type_name,sku_num
from
(selectdata.id,data.user_id,data.sku_id,date_format(from_utc_timestamp(ts*1000,'GMT+8'),'yyyy-MM-dd') date_id,date_format(from_utc_timestamp(ts*1000,'GMT+8'),'yyyy-MM-dd HH:mm:ss') create_time,data.source_id,data.source_type source_type_code,if(type='insert',data.sku_num,data.sku_num-old['sku_num']) sku_numfrom ods_cart_info_incwhere dt='2020-06-15'and (type='insert'or(type='update' and old['sku_num'] is not null and data.sku_num>cast(old['sku_num'] as int)))
)cart
left join
(selectdic_code,dic_name source_type_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code='24'
)dic
on cart.source_type_code=dic.dic_code;
这里在判断是否修改了 order_info 中 sku_num 值的时候我们还可以通过下面的方式来确认修改之前包含该字段:
type='update' and array_contains(map_keys(old),'sku_num')
这里我们不能使用 create_time 作为加购时间,因为 create_time 是我们创建这个购物车的时间,而不是真正的加购时间,所以我们应该用 ods_cart_info_inc 中的 ts 自动,因为它代表的是订单的变动时间:
对于不同的操作类型(insert 或 update)sku_num 的值也是不一样的,对于 insert 操作,这个商品是第一次加入购物车,所以 sku_num 就是加购的件数;但是对于 update,sku_num 指的是加购后的件数,所以实际加购的数量=old['sku_num']-data.sku_num 。
所以只要我们清楚了加购这个业务过程对订单表的影响,装载这张表就很简单了。
1.2、交易域下单事务事实表
同样分两步:设计表结构(4步),编写装载语句
1.2.1、设计表结构
- 选择业务过程:我们选择的是下单这个业务过程
- 声明粒度:要求尽可能最细粒度,所以这里的粒度应该是一个订单中的一个商品项而不是一整个订单,所以我们也就可以想到这张事实表对应业务系统中的 order_detail 表。
- 确认维度:关于下单操作,我们能想到的维度比如:时间、用户、商品、地区、活动和优惠券。以及下单方式,我们之前说过:如果某些维度表的维度属性很少(比如支付方式表没有必要去单独创建一个维度表,因为它就一个支付方式字段),则可不创建该维度表,而把该表的维度属性直接增加到与之相关的事实表中对于维度退化。确认维度是个灵活的过程,它并不取决于我们要分析什么指标,而是取决于我们业务系统能提供什么信息,比如这里的 order_detail 中包含了下单地区、该订单参与的活动以及使用优惠券的信息才能支持我们确认这样的维度。
- 确认事实:也就是确认度量值,这里的度量值可以是:下单件数、下单原始金额、下单的最终金额、活动优惠金额和优惠券优惠金额等。
1.2.2、建表语句
同样,这里的维度可以分为四部分:
- 从业务系统表当中直接拿过来的字段(比如 id、order_id 、create_time、source_id、source_type)
- 用作关联维度表的维度外键(user_id、sku_id、province_id、activity_id(算是退化字段,因为它和activity_rule_id都在同一张表,但是activity_rule_id的粒度更细)、activity_rule_id 、coupo_id、date_id等)
- 度量指标字段(比如 sku_num、split_xxx ...)
- 维度退化字段(比如 activity_id、source_type_code、source_id、source_type_name 等)
DROP TABLE IF EXISTS dwd_trade_order_detail_inc;
CREATE EXTERNAL TABLE dwd_trade_order_detail_inc
(`id` STRING COMMENT '编号',`order_id` STRING COMMENT '订单id',`user_id` STRING COMMENT '用户id',`sku_id` STRING COMMENT '商品id',`province_id` STRING COMMENT '省份id',`activity_id` STRING COMMENT '参与活动规则id',`activity_rule_id` STRING COMMENT '参与活动规则id',`coupon_id` STRING COMMENT '使用优惠券id',`date_id` STRING COMMENT '下单日期id',`create_time` STRING COMMENT '下单时间',`source_id` STRING COMMENT '来源编号',`source_type_code` STRING COMMENT '来源类型编码',`source_type_name` STRING COMMENT '来源类型名称',`sku_num` BIGINT COMMENT '商品数量',`split_original_amount` DECIMAL(16, 2) COMMENT '原始价格',`split_activity_amount` DECIMAL(16, 2) COMMENT '活动优惠分摊',`split_coupon_amount` DECIMAL(16, 2) COMMENT '优惠券优惠分摊',`split_total_amount` DECIMAL(16, 2) COMMENT '最终价格分摊'
) COMMENT '交易域下单明细事务事实表'PARTITIONED BY (`dt` STRING)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_trade_order_detail_inc/'TBLPROPERTIES ('orc.compress' = 'snappy');
1.2.3、数据流向
在数据装载之前我们同样需要了解数据的流向,下单这个业务过程会对哪些表产生影响我们就从哪个表去获取数据。
这里的下单这个操作会影响到 order_info(会插入一条订单数据)、order_detail(会插入多条数据,取决于订单的中的商品数量)、order_detail_coupon(用户使用优惠券就会在这张表中插入数据)和 order_detail_activity(用户参与活动就会往这张表插入数据)。
相比较上面的加购操作,这里的下单操作虽然涉及到的表更多,但是它都是插入(insert)操作,所以事实上装载的逻辑要比加购简单。同时我们依然要区分首日和每日的数据。
1.2.4、首日数据装载
逻辑还是比较简单的,就是把 order_detail 作为主表不断进行 left join :
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table dwd_trade_order_detail_inc partition (dt)
selectod.id,order_id,user_id,sku_id,province_id,activity_id,activity_rule_id,coupon_id,date_format(create_time, 'yyyy-MM-dd') date_id,create_time,source_id,source_type,dic_name,sku_num,split_original_amount,split_activity_amount,split_coupon_amount,split_total_amount,date_format(create_time,'yyyy-MM-dd')
from
(selectdata.id,data.order_id,data.sku_id,data.create_time,data.source_id,data.source_type,data.sku_num,data.sku_num * data.order_price split_original_amount,data.split_total_amount,data.split_activity_amount,data.split_coupon_amountfrom ods_order_detail_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) od
left join
(selectdata.id,data.user_id,data.province_idfrom ods_order_info_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) oi
on od.order_id = oi.id
left join
(selectdata.order_detail_id,data.activity_id,data.activity_rule_idfrom ods_order_detail_activity_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) act
on od.id = act.order_detail_id
left join
(selectdata.order_detail_id,data.coupon_idfrom ods_order_detail_coupon_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) cou
on od.id = cou.order_detail_id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code='24'
)dic
on od.source_type=dic.dic_code;
1.2.5、每日装载语句
对于之后的每日装载,我们只需要确保每张表的 type 为 insert、dt = 当天即可。
insert overwrite table dwd_trade_order_detail_inc partition (dt='2020-06-15')
selectod.id,order_id,user_id,sku_id,province_id,activity_id,activity_rule_id,coupon_id,date_id,create_time,source_id,source_type,dic_name,sku_num,split_original_amount,split_activity_amount,split_coupon_amount,split_total_amount
from
(selectdata.id,data.order_id,data.sku_id,date_format(data.create_time, 'yyyy-MM-dd') date_id,data.create_time,data.source_id,data.source_type,data.sku_num,data.sku_num * data.order_price split_original_amount,data.split_total_amount,data.split_activity_amount,data.split_coupon_amountfrom ods_order_detail_incwhere dt = '2020-06-15'and type = 'insert'
) od
left join
(selectdata.id,data.user_id,data.province_idfrom ods_order_info_incwhere dt = '2020-06-15'and type = 'insert'
) oi
on od.order_id = oi.id
left join
(selectdata.order_detail_id,data.activity_id,data.activity_rule_idfrom ods_order_detail_activity_incwhere dt = '2020-06-15'and type = 'insert'
) act
on od.id = act.order_detail_id
left join
(selectdata.order_detail_id,data.coupon_idfrom ods_order_detail_coupon_incwhere dt = '2020-06-15'and type = 'insert'
) cou
on od.id = cou.order_detail_id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code='24'
)dic
on od.source_type=dic.dic_code;
1.3、交易域取消订单事务事实表
注意是取消订单,不是退单,还没完成支付呢。
1.3.1、设计表结构
依然是那4个步骤:
- 选择业务过程:取消订单
- 声明粒度:谁+在什么时候+取消了哪个商品
- 确认维度:时间、用户、商品、地区、活动、优惠券
- 确认事实:取消的商品件数、取消的金额、下单的原始金额、最终下单金额、活动优惠金额、优惠券金额
可以看到,取消订单表的大部分维度和下单表是一致的,只是语义不一样而已。
1.3.2、建表语句
DROP TABLE IF EXISTS dwd_trade_cancel_detail_inc;
CREATE EXTERNAL TABLE dwd_trade_cancel_detail_inc
(`id` STRING COMMENT '编号',`order_id` STRING COMMENT '订单id',`user_id` STRING COMMENT '用户id',`sku_id` STRING COMMENT '商品id',`province_id` STRING COMMENT '省份id',`activity_id` STRING COMMENT '参与活动规则id',`activity_rule_id` STRING COMMENT '参与活动规则id',`coupon_id` STRING COMMENT '使用优惠券id',`date_id` STRING COMMENT '取消订单日期id',`cancel_time` STRING COMMENT '取消订单时间',`source_id` STRING COMMENT '来源编号',`source_type_code` STRING COMMENT '来源类型编码',`source_type_name` STRING COMMENT '来源类型名称',`sku_num` BIGINT COMMENT '商品数量',`split_original_amount` DECIMAL(16, 2) COMMENT '原始价格',`split_activity_amount` DECIMAL(16, 2) COMMENT '活动优惠分摊',`split_coupon_amount` DECIMAL(16, 2) COMMENT '优惠券优惠分摊',`split_total_amount` DECIMAL(16, 2) COMMENT '最终价格分摊'
) COMMENT '交易域取消订单明细事务事实表'PARTITIONED BY (`dt` STRING)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_trade_cancel_detail_inc/'TBLPROPERTIES ('orc.compress' = 'snappy');
1.3.3、 数据流向
我们需要知道取消订单会对哪些表产生影响,事实上,只会对 order_info 产生影响(改变 order_info 的 order_status 字段 ),同样,我们的首日数据(存在 ods_order_info_inc 当中)当中也存在一些取消订单的数据需要我们进行分区处理。
不同 order_status 对应的状态:
这里需要注意的是:对于已取消、已完成、退款完成这些操作表示的都是最终的操作,这些订单的状态不会再发生变化;但是对于未支付、已支付、退款中这些订单状态都还可能会发送变化。所以当我们在找支付成功的订单时,不仅要考虑订单状态是已支付状态,还有退款完成、已支付这些状态也经历过已支付状态。
1.3.4、首日数据装载
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table dwd_trade_cancel_detail_inc partition (dt)
selectod.id,order_id,user_id,sku_id,province_id,activity_id,activity_rule_id,coupon_id,date_format(canel_time,'yyyy-MM-dd') date_id,canel_time,source_id,source_type,dic_name,sku_num,split_original_amount,split_activity_amount,split_coupon_amount,split_total_amount,date_format(canel_time,'yyyy-MM-dd')
from
(selectdata.id,data.order_id,data.sku_id,data.source_id,data.source_type,data.sku_num,data.sku_num * data.order_price split_original_amount,data.split_total_amount,data.split_activity_amount,data.split_coupon_amountfrom ods_order_detail_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) od
join
(selectdata.id,data.user_id,data.province_id,data.operate_time canel_timefrom ods_order_info_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'and data.order_status='1003'
) oi
on od.order_id = oi.id
left join
(selectdata.order_detail_id,data.activity_id,data.activity_rule_idfrom ods_order_detail_activity_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) act
on od.id = act.order_detail_id
left join
(selectdata.order_detail_id,data.coupon_idfrom ods_order_detail_coupon_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) cou
on od.id = cou.order_detail_id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code='24'
)dic
on od.source_type=dic.dic_code;
1.3.5、每日数据装载
这里需要注意:我们取消的订单可能是前一天下的,所以我们从当天的 ods_order_detail_inc 中是查不到的,所以我们查询的时候还需要从当天的前一天订单明细中也查一份,而且为了和 order_detail 进行 join 关联(防止关联不上),我们的其它表也需要查询前一天的数据。
insert overwrite table dwd_trade_cancel_detail_inc partition (dt='2020-06-15')
selectod.id,order_id,user_id,sku_id,province_id,activity_id,activity_rule_id,coupon_id,date_format(canel_time,'yyyy-MM-dd') date_id,canel_time,source_id,source_type,dic_name,sku_num,split_original_amount,split_activity_amount,split_coupon_amount,split_total_amount
from
(selectdata.id,data.order_id,data.sku_id,data.source_id,data.source_type,data.sku_num,data.sku_num * data.order_price split_original_amount,data.split_total_amount,data.split_activity_amount,data.split_coupon_amountfrom ods_order_detail_inc -- 取消订单不会影响订单明细表where (dt='2020-06-15' or dt=date_add('2020-06-15',-1))and (type = 'insert' or type= 'bootstrap-insert') -- 过了第2天就可以不写bootstrap-insert
) od
join
(selectdata.id,data.user_id,data.province_id,data.operate_time canel_timefrom ods_order_info_incwhere dt = '2020-06-15'and type = 'update'and data.order_status='1003'and array_contains(map_keys(old),'order_status')
) oi
on order_id = oi.id
left join
(selectdata.order_detail_id,data.activity_id,data.activity_rule_idfrom ods_order_detail_activity_incwhere (dt='2020-06-15' or dt=date_add('2020-06-15',-1))and (type = 'insert' or type= 'bootstrap-insert')
) act
on od.id = act.order_detail_id
left join
(selectdata.order_detail_id,data.coupon_idfrom ods_order_detail_coupon_incwhere (dt='2020-06-15' or dt=date_add('2020-06-15',-1))and (type = 'insert' or type= 'bootstrap-insert')
) cou
on od.id = cou.order_detail_id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code='24'
)dic
on od.source_type=dic.dic_code;
1.4、交易域支付成功事务事实表
我们要求事实表对应的业务过程必须是原子操作的,也就是不可再切分的,所以这里说的是支付成功这个业务过程,而不是支付这个行为,因为支付可能成功、可能失败。
1.4.1、设计表结构
- 选择业务过程:支付成功
- 声明粒度:谁+什么时候+成功支付了哪个商品
- 确认维度:时间、用户、商品、地区、活动、优惠券、支付方式(维度退化)
- 确认事实:支付件数、支付金额、最终支付金额、活动优惠金额、优惠券金额
1.4.2、建表语句
DROP TABLE IF EXISTS dwd_trade_pay_detail_suc_inc;
CREATE EXTERNAL TABLE dwd_trade_pay_detail_suc_inc
(`id` STRING COMMENT '编号',`order_id` STRING COMMENT '订单id',`user_id` STRING COMMENT '用户id',`sku_id` STRING COMMENT '商品id',`province_id` STRING COMMENT '省份id',`activity_id` STRING COMMENT '参与活动规则id',`activity_rule_id` STRING COMMENT '参与活动规则id',`coupon_id` STRING COMMENT '使用优惠券id',`payment_type_code` STRING COMMENT '支付类型编码',`payment_type_name` STRING COMMENT '支付类型名称',`date_id` STRING COMMENT '支付日期id',`callback_time` STRING COMMENT '支付成功时间',`source_id` STRING COMMENT '来源编号',`source_type_code` STRING COMMENT '来源类型编码',`source_type_name` STRING COMMENT '来源类型名称',`sku_num` BIGINT COMMENT '商品数量',`split_original_amount` DECIMAL(16, 2) COMMENT '应支付原始金额',`split_activity_amount` DECIMAL(16, 2) COMMENT '支付活动优惠分摊',`split_coupon_amount` DECIMAL(16, 2) COMMENT '支付优惠券优惠分摊',`split_payment_amount` DECIMAL(16, 2) COMMENT '支付金额'
) COMMENT '交易域成功支付事务事实表'PARTITIONED BY (`dt` STRING)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_trade_pay_detail_suc_inc/'TBLPROPERTIES ('orc.compress' = 'snappy');
1.4.3、数据流向
支付成功会影响哪些表?其实只会影响一张表:当支付成功后 ,order_info 的 order_status 字段就会变成 1002,payment_info 的 order_status 字段会变成 1602 。
事实表要求我们的粒度是最小的,所以我们需要找到支付成功的订单明细,而不是订单信息。所以我们需要先从 order_info 中找到支付成功的订单,然后去和 order_detail 关联得到该订单的所有商品 ...
尽管支付成功后 order_info 的 order_status 字段会变成 1002,但是我们并不能把它当做过滤条件,因为它并不是一个最终状态,我们应该去 payment_info 去找更合适。
当我们点击支付按钮的时候,payment_info 中就会插入一条数据,此时的 payment_status 值为 1601 (待支付状态),create_time 也会插入当前时间;但是 callback_time 字段为空,callback_content 字段同样为空。当支付成功的时候, payment_status 值变为 1602 (支付成功状态), callback_time 字段为支付成功的时间,callback_content 字段为回调内容。
所以当我们每日装载数据的时候,就需要从 ods_payment_info_inc 的当日分区中过滤 type 为 update 的数据(首日装载另做判断,因为首日装载都是 bootstrap-insert ,我们需要判断支 payment_status )。
1.4.4、首日装载语句
insert overwrite table dwd_trade_pay_detail_suc_inc partition (dt)
selectod.id,od.order_id,user_id,sku_id,province_id,activity_id,activity_rule_id,coupon_id,payment_type,pay_dic.dic_name,date_format(callback_time,'yyyy-MM-dd') date_id,callback_time,source_id,source_type,src_dic.dic_name,sku_num,split_original_amount,split_activity_amount,split_coupon_amount,split_total_amount,date_format(callback_time,'yyyy-MM-dd')
from
(selectdata.id,data.order_id,data.sku_id,data.source_id,data.source_type,data.sku_num,data.sku_num * data.order_price split_original_amount,data.split_total_amount,data.split_activity_amount,data.split_coupon_amountfrom ods_order_detail_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) od
join
(selectdata.user_id,data.order_id,data.payment_type,data.callback_timefrom ods_payment_info_incwhere dt='2020-06-14'and type='bootstrap-insert'and data.payment_status='1602'
) pi
on od.order_id=pi.order_id
left join
(selectdata.id,data.province_idfrom ods_order_info_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) oi
on od.order_id = oi.id
left join
(selectdata.order_detail_id,data.activity_id,data.activity_rule_idfrom ods_order_detail_activity_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) act
on od.id = act.order_detail_id
left join
(selectdata.order_detail_id,data.coupon_idfrom ods_order_detail_coupon_incwhere dt = '2020-06-14'and type = 'bootstrap-insert'
) cou
on od.id = cou.order_detail_id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code='11'
) pay_dic
on pi.payment_type=pay_dic.dic_code
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code='24'
)src_dic
on od.source_type=src_dic.dic_code;
1.4.5、每日装载语句
insert overwrite table dwd_trade_pay_detail_suc_inc partition (dt='2020-06-15')
selectod.id,od.order_id,user_id,sku_id,province_id,activity_id,activity_rule_id,coupon_id,payment_type,pay_dic.dic_name,date_format(callback_time,'yyyy-MM-dd') date_id,callback_time,source_id,source_type,src_dic.dic_name,sku_num,split_original_amount,split_activity_amount,split_coupon_amount,split_total_amount
from
(selectdata.id,data.order_id,data.sku_id,data.source_id,data.source_type,data.sku_num,data.sku_num * data.order_price split_original_amount,data.split_total_amount,data.split_activity_amount,data.split_coupon_amountfrom ods_order_detail_incwhere (dt = '2020-06-15' or dt = date_add('2020-06-15',-1))and (type = 'insert' or type = 'bootstrap-insert')
) od
join
(selectdata.user_id,data.order_id,data.payment_type,data.callback_timefrom ods_payment_info_incwhere dt='2020-06-15'and type='update'and array_contains(map_keys(old),'payment_status')and data.payment_status='1602'
) pi
on od.order_id=pi.order_id
left join
(selectdata.id,data.province_idfrom ods_order_info_incwhere (dt = '2020-06-15')and (type = 'update')and array_contains(map_keys(old),'order_status')and order_status='1002'
) oi
on od.order_id = oi.id
left join
(selectdata.order_detail_id,data.activity_id,data.activity_rule_idfrom ods_order_detail_activity_incwhere (dt = '2020-06-15' or dt = date_add('2020-06-15',-1))and (type = 'insert' or type = 'bootstrap-insert')
) act
on od.id = act.order_detail_id
left join
(selectdata.order_detail_id,data.coupon_idfrom ods_order_detail_coupon_incwhere (dt = '2020-06-15' or dt = date_add('2020-06-15',-1))and (type = 'insert' or type = 'bootstrap-insert')
) cou
on od.id = cou.order_detail_id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code='11'
) pay_dic
on pi.payment_type=pay_dic.dic_code
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code='24'
)src_dic
on od.source_type=src_dic.dic_code;
1.5、交易域退单事务事实表
我们这里只考虑申请退单,不考虑申请退单后卖家怎么处理,退单状态怎么变化。
1.5.1、设计表结构
- 选择业务过程:退单
- 声明粒度:谁+什么时候+退了哪件商品
- 确认维度:时间、用户、商品、退单类型、退单原因类型
- 确认事实:退单件数、退单金额
1.5.2、建表语句
DROP TABLE IF EXISTS dwd_trade_order_refund_inc;
CREATE EXTERNAL TABLE dwd_trade_order_refund_inc
(`id` STRING COMMENT '编号',`user_id` STRING COMMENT '用户ID',`order_id` STRING COMMENT '订单ID',`sku_id` STRING COMMENT '商品ID',`province_id` STRING COMMENT '地区ID',`date_id` STRING COMMENT '日期ID',`create_time` STRING COMMENT '退单时间',`refund_type_code` STRING COMMENT '退单类型编码',`refund_type_name` STRING COMMENT '退单类型名称',`refund_reason_type_code` STRING COMMENT '退单原因类型编码',`refund_reason_type_name` STRING COMMENT '退单原因类型名称',`refund_reason_txt` STRING COMMENT '退单原因描述',`refund_num` BIGINT COMMENT '退单件数',`refund_amount` DECIMAL(16, 2) COMMENT '退单金额'
) COMMENT '交易域退单事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_trade_order_refund_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.5.3、数据流向
同样需要分析退单这个业务过程会对哪些表产生影响:order_info 的 order_status 字段会发生变化(变为 1005 (退款中)),order_refund_info 表中会插入一条数据。
1.5.4、首日装载
insert overwrite table dwd_trade_order_refund_inc partition(dt)
selectri.id,user_id,order_id,sku_id,province_id,date_format(create_time,'yyyy-MM-dd') date_id,create_time,refund_type,type_dic.dic_name,refund_reason_type,reason_dic.dic_name,refund_reason_txt,refund_num,refund_amount,date_format(create_time,'yyyy-MM-dd')
from
(selectdata.id,data.user_id,data.order_id,data.sku_id,data.refund_type,data.refund_num,data.refund_amount,data.refund_reason_type,data.refund_reason_txt,data.create_timefrom ods_order_refund_info_incwhere dt='2020-06-14'and type='bootstrap-insert'
)ri
left join
(selectdata.id,data.province_idfrom ods_order_info_inc --为了拿到 province_idwhere dt='2020-06-14'and type='bootstrap-insert'
)oi
on ri.order_id=oi.id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code = '15'
)type_dic
on ri.refund_type=type_dic.dic_code
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code = '13'
)reason_dic
on ri.refund_reason_type=reason_dic.dic_code;
1.5.5、每日装载语句
insert overwrite table dwd_trade_order_refund_inc partition(dt='2020-06-15')
selectri.id,user_id,order_id,sku_id,province_id,date_format(create_time,'yyyy-MM-dd') date_id,create_time,refund_type,type_dic.dic_name,refund_reason_type,reason_dic.dic_name,refund_reason_txt,refund_num,refund_amount
from
(selectdata.id,data.user_id,data.order_id,data.sku_id,data.refund_type,data.refund_num,data.refund_amount,data.refund_reason_type,data.refund_reason_txt,data.create_timefrom ods_order_refund_info_incwhere dt='2020-06-15'and type='insert'
)ri
left join
(selectdata.id,data.province_idfrom ods_order_info_inc where dt='2020-06-15'and type='update'and array_contains(map_keys(old),'old_status')and order_status='1005'
)oi
on ri.order_id=oi.id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code = '15'
)type_dic
on ri.refund_type=type_dic.dic_code
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code = '13'
)reason_dic
on ri.refund_reason_type=reason_dic.dic_code;
1.6、交易域退款成功事务事实表
1.6.1、设计表结构
- 选择业务过程:退款成功
- 声明粒度:谁+什么时候+哪件商品退款成功
- 确认维度:用户、地区、时间、商品、退款方式
- 确认事实:退款件数、退款金额
1.6.2、建表语句
DROP TABLE IF EXISTS dwd_trade_refund_pay_suc_inc;
CREATE EXTERNAL TABLE dwd_trade_refund_pay_suc_inc
(`id` STRING COMMENT '编号',`user_id` STRING COMMENT '用户ID',`order_id` STRING COMMENT '订单编号',`sku_id` STRING COMMENT 'SKU编号',`province_id` STRING COMMENT '地区ID',`payment_type_code` STRING COMMENT '支付类型编码',`payment_type_name` STRING COMMENT '支付类型名称',`date_id` STRING COMMENT '日期ID',`callback_time` STRING COMMENT '支付成功时间',`refund_num` DECIMAL(16, 2) COMMENT '退款件数',`refund_amount` DECIMAL(16, 2) COMMENT '退款金额'
) COMMENT '交易域提交退款成功事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_trade_refund_pay_suc_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.6.3、数据流向
用户退款会对哪些表产生影响:order_info 的 order_status 字段会发生变化(1006),refund_payment 的 redund_status 也会发生变化。
和上面的退单一样,我们都需要精确到商品,因为退单可能是退订单中的一件或多件商品。
1.6.4、首日装载
insert overwrite table dwd_trade_refund_pay_suc_inc partition(dt)
selectrp.id,user_id,rp.order_id,rp.sku_id,province_id,payment_type,dic_name,date_format(callback_time,'yyyy-MM-dd') date_id,callback_time,refund_num,total_amount,date_format(callback_time,'yyyy-MM-dd')
from
(selectdata.id,data.order_id,data.sku_id,data.payment_type,data.callback_time,data.total_amountfrom ods_refund_payment_incwhere dt='2020-06-14'and type = 'bootstrap-insert'and data.refund_status='1602'
)rp
left join
(selectdata.id,data.user_id,data.province_idfrom ods_order_info_incwhere dt='2020-06-14'and type='bootstrap-insert'
)oi
on rp.order_id=oi.id
left join
(selectdata.order_id,data.sku_id,data.refund_numfrom ods_order_refund_info_incwhere dt='2020-06-14'and type='bootstrap-insert'
)ri
on rp.order_id=ri.order_id
and rp.sku_id=ri.sku_id --必须保证同一订单同一商品
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code='11'
)dic
on rp.payment_type=dic.dic_code;
1.6.5、每日装载
insert overwrite table dwd_trade_refund_pay_suc_inc partition(dt='2020-06-15')
selectrp.id,user_id,rp.order_id,rp.sku_id,province_id,payment_type,dic_name,date_format(callback_time,'yyyy-MM-dd') date_id,callback_time,refund_num,total_amount
from
(selectdata.id,data.order_id,data.sku_id,data.payment_type,data.callback_time,data.total_amountfrom ods_refund_payment_incwhere dt='2020-06-15'and type = 'update'and array_contains(map_keys(old),'refund_status')and data.refund_status='1602'
)rp
left join
(selectdata.id,data.user_id,data.province_idfrom ods_order_info_incwhere dt='2020-06-15'and type='update'and array_contains(map_keys(old),'order_status')and data.order_status='1006'
)oi
on rp.order_id=oi.id
left join
(selectdata.order_id,data.sku_id,data.refund_numfrom ods_order_refund_info_incwhere dt='2020-06-15'and type='update'and array_contains(map_keys(old),'refund_status')and data.refund_status='0705'
)ri
on rp.order_id=ri.order_id
and rp.sku_id=ri.sku_id
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code='11'
)dic
on rp.payment_type=dic.dic_code;
1.7、交易域购物车周期快照表
所谓周期快照表,区别于我们前面的事务事实表,周期快照表是全量表,一般用来解决存量型指标(比如库存、余额等,这是事务事实表的不足)。
1.7.1、设计表结构
对于事务型事实表,通常都是一张事实表对应一个业务过程;对于累积快照事实表,一张表对应多个业务过程。对于这里的周期快照表,并没有讨论的意义。因为无法确定它对应几个业务过程。
DROP TABLE IF EXISTS dwd_trade_cart_full;
CREATE EXTERNAL TABLE dwd_trade_cart_full
(`id` STRING COMMENT '编号',`user_id` STRING COMMENT '用户id',`sku_id` STRING COMMENT '商品id',`sku_name` STRING COMMENT '商品名称',`sku_num` BIGINT COMMENT '加购物车件数'
) COMMENT '交易域购物车周期快照事实表'PARTITIONED BY (`dt` STRING)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_trade_cart_full/'TBLPROPERTIES ('orc.compress' = 'snappy');
1.7.2、装载语句
对于购物车表,我们在 ODS 层建了两张表(增量和全量),这里我们选择全量表作为数据来源:
insert overwrite table dwd_trade_cart_full partition(dt='2020-06-14')
selectid,user_id,sku_id,sku_name,sku_num
from ods_cart_info_full
where dt='2020-06-14'
and is_ordered='0';
这里的 is_ordered 是一个删除标记,当它的值为 1 的时候代表用户已下单,也就意味着从购物车删除了该商品。所以我们每日装载的时候需要注意筛选出 is_ordered = 0 的数据。
1.8、工具域优惠券领取事务事实表
有关优惠券的业务过程:领券、使用券下单、使用券支付。它们都会对表 coupon_use 产生影响:领券后表 coupon_use 会插入一条新的记录;使用券下单时会对字段 order_id、coupon_status、using_time 都会发生变化;使用券支付时会对字段 coupon_status、used_time 发生变化。
1.8.1、建表语句
需要注意,对于优惠券事实表,它并没有一个明显的度量值。虽然我们说事实表是由维度外键和度量值组成的,但规矩也不是死的,这里的度量值是隐含的,也就是一行代表一个优惠券的记录。
DROP TABLE IF EXISTS dwd_tool_coupon_get_inc;
CREATE EXTERNAL TABLE dwd_tool_coupon_get_inc
(`id` STRING COMMENT '编号',`coupon_id` STRING COMMENT '优惠券ID',`user_id` STRING COMMENT 'userid',`date_id` STRING COMMENT '日期ID',`get_time` STRING COMMENT '领取时间'
) COMMENT '优惠券领取事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_tool_coupon_get_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.8.2、首日装载
insert overwrite table dwd_tool_coupon_get_inc partition(dt)
selectdata.id,data.coupon_id,data.user_id,date_format(data.get_time,'yyyy-MM-dd') date_id,data.get_time,date_format(data.get_time,'yyyy-MM-dd')
from ods_coupon_use_inc
where dt='2020-06-14'
and type='bootstrap-insert';
1.8.3、每日装载
只要是 insert 就一定是领券操作,只要是 update 就是使用券操作。
insert overwrite table dwd_tool_coupon_get_inc partition (dt='2020-06-15')
selectdata.id,data.coupon_id,data.user_id,date_format(data.get_time,'yyyy-MM-dd') date_id,data.get_time
from ods_coupon_use_inc
where dt='2020-06-15'
and type='insert';
1.9、工具域优惠券使用(下单)事务事实表
1.9.1、建表语句
DROP TABLE IF EXISTS dwd_tool_coupon_order_inc;
CREATE EXTERNAL TABLE dwd_tool_coupon_order_inc
(`id` STRING COMMENT '编号',`coupon_id` STRING COMMENT '优惠券ID',`user_id` STRING COMMENT 'user_id',`order_id` STRING COMMENT 'order_id',`date_id` STRING COMMENT '日期ID',`order_time` STRING COMMENT '使用下单时间'
) COMMENT '优惠券使用下单事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_tool_coupon_order_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.9.2、首日装载
根据 used_time 是否为空或者 coupon_status 是否等于 1402 都是可以的。
insert overwrite table dwd_tool_coupon_order_inc partition(dt)
selectdata.id,data.coupon_id,data.user_id,data.order_id,date_format(data.using_time,'yyyy-MM-dd') date_id,data.using_time,date_format(data.using_time,'yyyy-MM-dd')
from ods_coupon_use_inc
where dt='2020-06-14'
and type='bootstrap-insert'
and data.using_time is not null;
1.9.3、每日装载
insert overwrite table dwd_tool_coupon_order_inc partition(dt='2020-06-15')
selectdata.id,data.coupon_id,data.user_id,data.order_id,date_format(data.using_time,'yyyy-MM-dd') date_id,data.using_time
from ods_coupon_use_inc
where dt='2020-06-15'
and type='update'
and array_contains(map_keys(old),'using_time');
1.10、工具域优惠券使用(支付)事务事实表
1.10.1、建表语句
DROP TABLE IF EXISTS dwd_tool_coupon_pay_inc;
CREATE EXTERNAL TABLE dwd_tool_coupon_pay_inc
(`id` STRING COMMENT '编号',`coupon_id` STRING COMMENT '优惠券ID',`user_id` STRING COMMENT 'user_id',`order_id` STRING COMMENT 'order_id',`date_id` STRING COMMENT '日期ID',`payment_time` STRING COMMENT '使用下单时间'
) COMMENT '优惠券使用支付事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_tool_coupon_pay_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.10.2、首日装载
根据 used_time 是否为空或者 coupon_status 是否等于 1403 都是可以的。
insert overwrite table dwd_tool_coupon_pay_inc partition(dt)
selectdata.id,data.coupon_id,data.user_id,data.order_id,date_format(data.used_time,'yyyy-MM-dd') date_id,data.used_time,date_format(data.used_time,'yyyy-MM-dd')
from ods_coupon_use_inc
where dt='2020-06-14'
and type='bootstrap-insert'
and data.used_time is not null;
1.10.3、每日装载
insert overwrite table dwd_tool_coupon_pay_inc partition(dt='2020-06-15')
selectdata.id,data.coupon_id,data.user_id,data.order_id,date_format(data.used_time,'yyyy-MM-dd') date_id,data.used_time
from ods_coupon_use_inc
where dt='2020-06-15'
and type='update'
and array_contains(map_keys(old),'used_time');
1.11、互动域收藏商品事务事实表
1.11.1、建表语句
对于商品收藏,同样没有度量字段,因为一行就相当于一个隐含的度量值——一个商品收藏。
DROP TABLE IF EXISTS dwd_interaction_favor_add_inc;
CREATE EXTERNAL TABLE dwd_interaction_favor_add_inc
(`id` STRING COMMENT '编号',`user_id` STRING COMMENT '用户id',`sku_id` STRING COMMENT 'sku_id',`date_id` STRING COMMENT '日期id',`create_time` STRING COMMENT '收藏时间'
) COMMENT '收藏事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_interaction_favor_add_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.11.2、首日装载
同样我们需要分析收藏这个行为会对哪些表产生影响:事实上只会对 favor_info 产生影响:
当用户收藏商品的时候,favor_info 中会插入一条数据,当用户取消收藏时,favor_info 中的 is_cancal 字段修改为为 1 ,同时 cancal_time 设置为当前时间。所以只要 type=insert 就是商品收藏操作,只要是 type=update 并且 is_cancal=1 并且 cancal_time is not null 那么就是取消收藏操作(但是我们这里并没有建立取消收藏事实表)。
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table dwd_interaction_favor_add_inc partition(dt)
selectdata.id,data.user_id,data.sku_id,date_format(data.create_time,'yyyy-MM-dd') date_id,data.create_time,date_format(data.create_time,'yyyy-MM-dd')
from ods_favor_info_inc
where dt='2020-06-14'
and type = 'bootstrap-insert';
1.11.3、每日装载
insert overwrite table dwd_interaction_favor_add_inc partition(dt='2020-06-15')
selectdata.id,data.user_id,data.sku_id,date_format(data.create_time,'yyyy-MM-dd') date_id,data.create_time
from ods_favor_info_inc
where dt='2020-06-15'
and type = 'insert';
1.12、互动域评价事务事实表
1.12.1、建表语句
我们的这张表的粒度应该是 谁+什么时候+哪个订单+哪个商品+评论相关的维度属性/度量(比如好评还是差评,可以算是一个维度(被用在 SQL 的 where 过滤条件当中)也可以算是度量值(被用在聚合函数当中),具体看使用场景)
DROP TABLE IF EXISTS dwd_interaction_comment_inc;
CREATE EXTERNAL TABLE dwd_interaction_comment_inc
(`id` STRING COMMENT '编号',`user_id` STRING COMMENT '用户ID',`sku_id` STRING COMMENT 'sku_id',`order_id` STRING COMMENT '订单ID',`date_id` STRING COMMENT '日期ID',`create_time` STRING COMMENT '评价时间',`appraise_code` STRING COMMENT '评价编码',`appraise_name` STRING COMMENT '评价名称(好评/中评/差评/自动)'
) COMMENT '评价事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_interaction_comment_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.12.2、首日装载
insert overwrite table dwd_interaction_comment_inc partition(dt)
selectid,user_id,sku_id,order_id,date_format(create_time,'yyyy-MM-dd') date_id,create_time,appraise,dic_name,date_format(create_time,'yyyy-MM-dd')
from
(selectdata.id,data.user_id,data.sku_id,data.order_id,data.create_time,data.appraisefrom ods_comment_info_incwhere dt='2020-06-14'and type='bootstrap-insert'
)ci
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-14'and parent_code='12'
)dic
on ci.appraise=dic.dic_code;
1.12.3、每日装载
insert overwrite table dwd_interaction_comment_inc partition(dt='2020-06-15')
selectid,user_id,sku_id,order_id,date_format(create_time,'yyyy-MM-dd') date_id,create_time,appraise,dic_name
from
(selectdata.id,data.user_id,data.sku_id,data.order_id,data.create_time,data.appraisefrom ods_comment_info_incwhere dt='2020-06-15'and type='insert'
)ci
left join
(selectdic_code,dic_namefrom ods_base_dic_fullwhere dt='2020-06-15'and parent_code='12'
)dic
on ci.appraise=dic.dic_code;
1.13、流量域页面浏览事务事实表
流量域里面的业务过程一般都来自用户行为日志,因为我们一个网页的访问量、一个按钮的点击量不会存到数据库,一般都是前端埋点写到日志里面。
1.13.1、建表语句
这里我们的业务过程(页面浏览)的粒度是 谁(这里指的主要是设备id,因为很多时候并不需要登录才能浏览) + 什么时候 + 浏览了哪个页面
DROP TABLE IF EXISTS dwd_traffic_page_view_inc;
CREATE EXTERNAL TABLE dwd_traffic_page_view_inc
(`province_id` STRING COMMENT '省份id',`brand` STRING COMMENT '手机品牌',`channel` STRING COMMENT '渠道',`is_new` STRING COMMENT '是否首次启动',`model` STRING COMMENT '手机型号',`mid_id` STRING COMMENT '设备id',`operate_system` STRING COMMENT '操作系统',`user_id` STRING COMMENT '会员id',`version_code` STRING COMMENT 'app版本号',`page_item` STRING COMMENT '目标id ',`page_item_type` STRING COMMENT '目标类型',`last_page_id` STRING COMMENT '上页类型',`page_id` STRING COMMENT '页面ID ',`source_type` STRING COMMENT '来源类型',`date_id` STRING COMMENT '日期id',`view_time` STRING COMMENT '跳入时间',`session_id` STRING COMMENT '所属会话id',`during_time` BIGINT COMMENT '持续时间毫秒'
) COMMENT '页面日志表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_traffic_page_view_inc'TBLPROPERTIES ('orc.compress' = 'snappy');
这里从 province_id 到 version_code 字段都是从日志中退化到事实表的字段,它们存在于日志数据中的 common 属性里面,都是环境信息。这里退化的原因是我们的数据源是日志,而日志中既包含了环境这些维度信息,又包含了业务过程信息,如果把它们单独分开分别创建多张维度表和事实表的话,当我们需要从日志中读取再装载的时候,又需要把它们 join 起来。所以反正它们本来就在一起,还不如直接放一起做一个维度退化,免得分开还得 join。
这张表几乎所有字段都来组我们的日志数据,除了 province_id 需要通过关联 area_code 来间接得到、session_id 需要加工得到。
1.13.2、数据装载
我们需要通过判断 page 属性是否为空来判断当前日志是不是页面日志(page 为空是启动日志,不为空则为页面日志)。
关于页面浏览表的分区,因为我们的页面浏览是没有历史数据的,所以我们并不需要首日、每日装载的区分。
set hive.cbo.enable=false;
insert overwrite table dwd_traffic_page_view_inc partition (dt='2020-06-14')
selectprovince_id,brand,channel,is_new,model,mid_id,operate_system,user_id,version_code,page_item,page_item_type,last_page_id,page_id,source_type,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd') date_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd HH:mm:ss') view_time,concat(mid_id,'-',last_value(session_start_point,true) over (partition by mid_id order by ts)) session_id,during_time
from
(selectcommon.ar area_code,common.ba brand,common.ch channel,common.is_new is_new,common.md model,common.mid mid_id,common.os operate_system,common.uid user_id,common.vc version_code,page.during_time,page.item page_item,page.item_type page_item_type,page.last_page_id,page.page_id,page.source_type,ts,if(page.last_page_id is null,ts,null) session_start_pointfrom ods_log_incwhere dt='2020-06-14'and page is not null
)log
left join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-14'
)bp
on log.area_code=bp.area_code;
Bug - struct is not null
描述:
例如struct是一个结构体,它有一些字段比如user_id,page_id等等,在Hive3.x版本中,使用struct is not null时没有把结构体为null的数据筛选掉。
原因:
这是Hive3.x中的一个bug,在语句的执行计划中,这个判断结构体是否为空的过滤条件直接被忽略了。
在数据库中,有RBO(基于规则的优化策略)和CBO(基于代价的优化策略)两种优化策略。实际上就是因为CBO这个优化策略导致的,Hive中默认使用了CBO优化策略。
解决方案:
1)方案一:已知了结构体struct里的字段名称,直接判断结构体里的字段是否为null即可
2)方案二:在Hive4.0版本中修复了此bug,因此使用Hive4.0版本即可,或者根据Hive4.0修复这部分的代码,在自己所用的Hive版本中修改对应的代码
3)方案三:在Hive中禁用CBO优化set hive.cbo.enable=false;
1.14、流量域启动事务事实表
对于启动这个操作而言,只有移动端的应用才有这个操作,PC 端并没有。
1.14.1、建表语句
DROP TABLE IF EXISTS dwd_traffic_start_inc;
CREATE EXTERNAL TABLE dwd_traffic_start_inc
(`province_id` STRING COMMENT '省份id',`brand` STRING COMMENT '手机品牌',`channel` STRING COMMENT '渠道',`is_new` STRING COMMENT '是否首次启动',`model` STRING COMMENT '手机型号',`mid_id` STRING COMMENT '设备id',`operate_system` STRING COMMENT '操作系统',`user_id` STRING COMMENT '会员id',`version_code` STRING COMMENT 'app版本号',`entry` STRING COMMENT 'icon手机图标 notice 通知',`open_ad_id` STRING COMMENT '广告页ID ',`date_id` STRING COMMENT '日期id',`start_time` STRING COMMENT '启动时间',`loading_time_ms` BIGINT COMMENT '启动加载时间',`open_ad_ms` BIGINT COMMENT '广告总共播放时间',`open_ad_skip_ms` BIGINT COMMENT '用户跳过广告时点'
) COMMENT '启动日志表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_traffic_start_inc'TBLPROPERTIES ('orc.compress' = 'snappy');
1.14.2、数据装载
同样,为了防止结构体字段 is not null 不生效,这里需要关闭 CBO :
set hive.cbo.enable=false;
insert overwrite table dwd_traffic_start_inc partition(dt='2020-06-14')
selectprovince_id,brand,channel,is_new,model,mid_id,operate_system,user_id,version_code,entry,open_ad_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd') date_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd HH:mm:ss') action_time,loading_time,open_ad_ms,open_ad_skip_ms
from
(selectcommon.ar area_code,common.ba brand,common.ch channel,common.is_new,common.md model,common.mid mid_id,common.os operate_system,common.uid user_id,common.vc version_code,`start`.entry,`start`.loading_time,`start`.open_ad_id,`start`.open_ad_ms,`start`.open_ad_skip_ms,tsfrom ods_log_incwhere dt='2020-06-14'and `start` is not null
)log
left join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-14'
)bp
on log.area_code=bp.area_code;
注意: 我们这里的 start 是 hive 中的关键字所以需要使用反引号,但是这个命令将来会被写到 shell 脚本里,而在 shell 脚本中反引号是执行反引号中的 shell 命令的意思,所以到时候我们还需要通过 \` 来转义。
1.15、流量域动作事务事实表
这里的动作主要采集的是用户的领券、加购、收藏这些动作。
1.15.1、建表语句
DROP TABLE IF EXISTS dwd_traffic_action_inc;
CREATE EXTERNAL TABLE dwd_traffic_action_inc
(`province_id` STRING COMMENT '省份id',`brand` STRING COMMENT '手机品牌',`channel` STRING COMMENT '渠道',`is_new` STRING COMMENT '是否首次启动',`model` STRING COMMENT '手机型号',`mid_id` STRING COMMENT '设备id',`operate_system` STRING COMMENT '操作系统',`user_id` STRING COMMENT '会员id',`version_code` STRING COMMENT 'app版本号',`during_time` BIGINT COMMENT '持续时间毫秒',`page_item` STRING COMMENT '目标id ',`page_item_type` STRING COMMENT '目标类型',`last_page_id` STRING COMMENT '上页类型',`page_id` STRING COMMENT '页面id ',`source_type` STRING COMMENT '来源类型',`action_id` STRING COMMENT '动作id',`action_item` STRING COMMENT '目标id ',`action_item_type` STRING COMMENT '目标类型',`date_id` STRING COMMENT '日期id',`action_time` STRING COMMENT '动作发生时间'
) COMMENT '动作日志表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_traffic_action_inc'TBLPROPERTIES ('orc.compress' = 'snappy');
1.15.2、装载语句
set hive.cbo.enable=false;
insert overwrite table dwd_traffic_action_inc partition(dt='2020-06-14')
selectprovince_id,brand,channel,is_new,model,mid_id,operate_system,user_id,version_code,during_time,page_item,page_item_type,last_page_id,page_id,source_type,action_id,action_item,action_item_type,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd') date_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd HH:mm:ss') action_time
from
(selectcommon.ar area_code,common.ba brand,common.ch channel,common.is_new,common.md model,common.mid mid_id,common.os operate_system,common.uid user_id,common.vc version_code,page.during_time,page.item page_item,page.item_type page_item_type,page.last_page_id,page.page_id,page.source_type,action.action_id,action.item action_item,action.item_type action_item_type,action.tsfrom ods_log_inc lateral view explode(actions) tmp as actionwhere dt='2020-06-14'and actions is not null
)log
left join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-14'
)bp
on log.area_code=bp.area_code;
注意:这里用到了炸裂函数,关于炸裂函数的使用仅仅这里写一遍是远远不够的,还是得下去多练!
1.16、流量域曝光事务事实表
这里的曝光指的是系统给我们推送内容的曝光行为,比如广告、轮播图、推荐。
1.16.1、建表语句
DROP TABLE IF EXISTS dwd_traffic_display_inc;
CREATE EXTERNAL TABLE dwd_traffic_display_inc
(`province_id` STRING COMMENT '省份id',`brand` STRING COMMENT '手机品牌',`channel` STRING COMMENT '渠道',`is_new` STRING COMMENT '是否首次启动',`model` STRING COMMENT '手机型号',`mid_id` STRING COMMENT '设备id',`operate_system` STRING COMMENT '操作系统',`user_id` STRING COMMENT '会员id',`version_code` STRING COMMENT 'app版本号',`during_time` BIGINT COMMENT 'app版本号',`page_item` STRING COMMENT '目标id ',`page_item_type` STRING COMMENT '目标类型',`last_page_id` STRING COMMENT '上页类型',`page_id` STRING COMMENT '页面ID ',`source_type` STRING COMMENT '来源类型',`date_id` STRING COMMENT '日期id',`display_time` STRING COMMENT '曝光时间',`display_type` STRING COMMENT '曝光类型',`display_item` STRING COMMENT '曝光对象id ',`display_item_type` STRING COMMENT 'app版本号',`display_order` BIGINT COMMENT '曝光顺序',`display_pos_id` BIGINT COMMENT '曝光位置'
) COMMENT '曝光日志表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_traffic_display_inc'TBLPROPERTIES ('orc.compress' = 'snappy');
这里我们的曝光时间 直接取的进入页面的时间,因为我们模拟的前端埋点并没有添加曝光时间属性。
1.16.2、装载语句
set hive.cbo.enable=false;
insert overwrite table dwd_traffic_display_inc partition(dt='2020-06-14')
selectprovince_id,brand,channel,is_new,model,mid_id,operate_system,user_id,version_code,during_time,page_item,page_item_type,last_page_id,page_id,source_type,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd') date_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd HH:mm:ss') display_time,display_type,display_item,display_item_type,display_order,display_pos_id
from
(selectcommon.ar area_code,common.ba brand,common.ch channel,common.is_new,common.md model,common.mid mid_id,common.os operate_system,common.uid user_id,common.vc version_code,page.during_time,page.item page_item,page.item_type page_item_type,page.last_page_id,page.page_id,page.source_type,display.display_type,display.item display_item,display.item_type display_item_type,display.`order` display_order,display.pos_id display_pos_id,tsfrom ods_log_inc lateral view explode(displays) tmp as displaywhere dt='2020-06-14'and displays is not null
)log
left join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-14'
)bp
on log.area_code=bp.area_code;
1.17、流量域错误事务事实表
错误日志有可能是来自于页面日志,也有可能是来自启动日志。
1.17.1、建表语句
DROP TABLE IF EXISTS dwd_traffic_error_inc;
CREATE EXTERNAL TABLE dwd_traffic_error_inc
(`province_id` STRING COMMENT '地区编码',`brand` STRING COMMENT '手机品牌',`channel` STRING COMMENT '渠道',`is_new` STRING COMMENT '是否首次启动',`model` STRING COMMENT '手机型号',`mid_id` STRING COMMENT '设备id',`operate_system` STRING COMMENT '操作系统',`user_id` STRING COMMENT '会员id',`version_code` STRING COMMENT 'app版本号',`page_item` STRING COMMENT '目标id ',`page_item_type` STRING COMMENT '目标类型',`last_page_id` STRING COMMENT '上页类型',`page_id` STRING COMMENT '页面ID ',`source_type` STRING COMMENT '来源类型',`entry` STRING COMMENT 'icon手机图标 notice 通知',`loading_time` STRING COMMENT '启动加载时间',`open_ad_id` STRING COMMENT '广告页ID ',`open_ad_ms` STRING COMMENT '广告总共播放时间',`open_ad_skip_ms` STRING COMMENT '用户跳过广告时点',`actions` ARRAY<STRUCT<action_id:STRING,item:STRING,item_type:STRING,ts:BIGINT>> COMMENT '动作信息',`displays` ARRAY<STRUCT<display_type :STRING,item :STRING,item_type :STRING,`order` :STRING,pos_id:STRING>> COMMENT '曝光信息',`date_id` STRING COMMENT '日期id',`error_time` STRING COMMENT '错误时间',`error_code` STRING COMMENT '错误码',`error_msg` STRING COMMENT '错误信息'
) COMMENT '错误日志表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_traffic_error_inc'TBLPROPERTIES ('orc.compress' = 'snappy');
这里的 actions 和 displays 字段并没有使用炸裂函数,因为这样会破坏我们的粒度,如果将来需要用到这个数据的时候就需要炸裂函数了。
1.17.2、装载语句
set hive.cbo.enable=false;
set hive.execution.engine=mr;
insert overwrite table dwd_traffic_error_inc partition(dt='2020-06-14')
selectprovince_id,brand,channel,is_new,model,mid_id,operate_system,user_id,version_code,page_item,page_item_type,last_page_id,page_id,source_type,entry,loading_time,open_ad_id,open_ad_ms,open_ad_skip_ms,actions,displays,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd') date_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd HH:mm:ss') error_time,error_code,error_msg
from
(selectcommon.ar area_code,common.ba brand,common.ch channel,common.is_new,common.md model,common.mid mid_id,common.os operate_system,common.uid user_id,common.vc version_code,page.during_time,page.item page_item,page.item_type page_item_type,page.last_page_id,page.page_id,page.source_type,`start`.entry,`start`.loading_time,`start`.open_ad_id,`start`.open_ad_ms,`start`.open_ad_skip_ms,actions,displays,err.error_code,err.msg error_msg,tsfrom ods_log_incwhere dt='2020-06-14'and err is not null
)log
join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-14'
)bp
on log.area_code=bp.area_code;
set hive.execution.engine=spark;
注意:这里的 actions、displays 对应到 Java 中是 List 类型,在 Hive On Spark 中报错 不期望的类型的列,这是 Hive On Spark 的一个 bug,我们需要通过切换 Hvie 引擎来解决。
1.18、用户域用户注册事务事实表
1.18.1、建表语句
DROP TABLE IF EXISTS dwd_user_register_inc;
CREATE EXTERNAL TABLE dwd_user_register_inc
(`user_id` STRING COMMENT '用户ID',`date_id` STRING COMMENT '日期ID',`create_time` STRING COMMENT '注册时间',`channel` STRING COMMENT '应用下载渠道',`province_id` STRING COMMENT '省份id',`version_code` STRING COMMENT '应用版本',`mid_id` STRING COMMENT '设备id',`brand` STRING COMMENT '设备品牌',`model` STRING COMMENT '设备型号',`operate_system` STRING COMMENT '设备操作系统'
) COMMENT '用户域用户注册事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_user_register_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.18.2、首日装载
注册成功会影响哪些表:显然用户信息表会插入一条记录,日志中会多一条页面为注册页面并且带有用户id的记日志录。
但是如果只用 user_info 去设计这张注册事实表维度信息太少了(只有用户和时间维度),我们希望尽可能丰富维度属性,所以这里我们会从地区表、日志表去获取更多的维度信息。
这里我们的表当然是以表为主,而不是以日志为主,因为业务数据更加靠谱(比如业务数据库有事务保证,比如涉及到金额的操作,我们绝不会用日志去传输)
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite table dwd_user_register_inc partition(dt)
selectui.user_id,date_format(create_time,'yyyy-MM-dd') date_id,create_time,channel,province_id,version_code,mid_id,brand,model,operate_system,date_format(create_time,'yyyy-MM-dd')
from
(selectdata.id user_id,data.create_timefrom ods_user_info_incwhere dt='2020-06-14'and type='bootstrap-insert'
)ui
left join
(selectcommon.ar area_code,common.ba brand,common.ch channel,common.md model,common.mid mid_id,common.os operate_system,common.uid user_id,common.vc version_codefrom ods_log_incwhere dt='2020-06-14'and page.page_id='register'and common.uid is not null
)log
on ui.user_id=log.user_id
left join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-14'
)bp
on log.area_code=bp.area_code;
1.18.3、每日装载
insert overwrite table dwd_user_register_inc partition(dt='2020-06-15')
selectui.user_id,date_format(create_time,'yyyy-MM-dd') date_id,create_time,channel,province_id,version_code,mid_id,brand,model,operate_system
from
(selectdata.id user_id,data.create_timefrom ods_user_info_incwhere dt='2020-06-15'and type='insert'
)ui
left join
(selectcommon.ar area_code,common.ba brand,common.ch channel,common.md model,common.mid mid_id,common.os operate_system,common.uid user_id,common.vc version_codefrom ods_log_incwhere dt='2020-06-15'and page.page_id='register'and common.uid is not null
)log
on ui.user_id=log.user_id
left join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-15'
)bp
on log.area_code=bp.area_code;
1.19、用户域用户登录事务事实表
1.19.1、建表语句
DROP TABLE IF EXISTS dwd_user_login_inc;
CREATE EXTERNAL TABLE dwd_user_login_inc
(`user_id` STRING COMMENT '用户ID',`date_id` STRING COMMENT '日期ID',`login_time` STRING COMMENT '登录时间',`channel` STRING COMMENT '应用下载渠道',`province_id` STRING COMMENT '省份id',`version_code` STRING COMMENT '应用版本',`mid_id` STRING COMMENT '设备id',`brand` STRING COMMENT '设备品牌',`model` STRING COMMENT '设备型号',`operate_system` STRING COMMENT '设备操作系统'
) COMMENT '用户域用户登录事务事实表'PARTITIONED BY (`dt` STRING)STORED AS ORCLOCATION '/warehouse/gmall/dwd/dwd_user_login_inc/'TBLPROPERTIES ("orc.compress" = "snappy");
1.19.2、数据装载
首先,数据来自于日志,日志没有历史数据,所以我们并不需要区分首日次日。
我们的登录场景根据日志中有没有 user_id 可以大致分我三类:从始至终没有登录、浏览到一半登录,打开网页时后台自动登录。
insert overwrite table dwd_user_login_inc partition(dt='2020-06-14')
selectuser_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd') date_id,date_format(from_utc_timestamp(ts,'GMT+8'),'yyyy-MM-dd HH:mm:ss') login_time,channel,province_id,version_code,mid_id,brand,model,operate_system
from
(selectuser_id,channel,area_code,version_code,mid_id,brand,model,operate_system,tsfrom(selectuser_id,channel,area_code,version_code,mid_id,brand,model,operate_system,ts,row_number() over (partition by session_id order by ts) rnfrom(selectuser_id,channel,area_code,version_code,mid_id,brand,model,operate_system,ts,concat(mid_id,'-',last_value(session_start_point,true) over(partition by mid_id order by ts)) session_idfrom(selectcommon.uid user_id,common.ch channel,common.ar area_code,common.vc version_code,common.mid mid_id,common.ba brand,common.md model,common.os operate_system,ts,if(page.last_page_id is null,ts,null) session_start_pointfrom ods_log_incwhere dt='2020-06-14'and page is not null)t1)t2where user_id is not null)t3where rn=1
)t4
left join
(selectid province_id,area_codefrom ods_base_province_fullwhere dt='2020-06-14'
)bp
on t4.area_code=bp.area_code;
脚本省略,注意:严格模式最好直接配到配置文件里去:
<property><name>hive.exec.dynamic.partition.mode</name><value>nonstrict</value>
</property>
总结
至此,DWD 层搭建完毕,DWD 层一般都是和 DIM 层配置使用的,一个负责提供业务过程相关行为信息,一个负责提供该业务过程的维度信息。
距离 DIM 层开发过去了整整两周,这两部分内容太重要了,所以学的很慢,但还是要回头慢慢再消化消化。