数仓知识

一、 离线数仓

应用:大屏、驾驶舱、商品分析、销售订单分析、BI平台、供应链仓储运单分析、门店经营分析、用户画像。

电商业务表

1. 建模理论

ER模型 :实体关系 3NF 数据冗余低 保障数据一致性 面向对象设计 主外键 不适合统计分析 ODS层
维度模型 :星型模型 建表&SQL

  • 数据统计:汇总的数据(行为产生的结果)
  • 数据分析:维度(角度)

数仓分层

ODS: 列存 gzip(压缩率高)默认支持 每天采集到的数据存入当天分区
DIM: 全量表:每天一份全量数据存入当天分区;拉链表:结束时间作为分区字段,正常就存9999-12-31中
DWD: 事务型:每天一份增量数据存当天分区; 周期型:每天全量;累积:业务流程中最后的时间作为分区字段,如果没结束就写入9999-12-31分区。
DWS: 预聚合 分区
ADS: 存Mysql 不分区 行式存储 tsv格式 gizp

2. DIM维度表

列存ORC+snapp压缩(效率快) 分区
维度退化:理论一个维度一张表,但是从效率上相关的属性设置为一张表,特别独立字段直接退化到事实表
主维表 相关维表 尽可能多的能分析的字段 编码和文字共存
维度变化:实时不考虑,离线要考虑每天保留全量 → 全量快照表 / 拉链表(缓慢变化维)
多值维度:事实表的一条记录对应维度表的多条记录 → 降低事实表的粒度(order_info维度一个订单降为一个sku)
多值属性:维度表某个属性有多个值 key1:value1,key2:value2

dim_sku_full

dim_user_zip

3. DWD事实表

维度 + 度量值 将行为描述清楚

建表步骤

  • 选择业务过程 → 表 → dwd_trade_cart_add_inc
  • 声明粒度 → 行 → 用户 + 时间 + 商品 + 数量
  • 确认维度 → 列 → user + date + sku + num
  • 确认事实 → 度量值 → num + count

表分类

  • 事务型 → 原子操作(login_success login_fail) 粒度越细(维度越多) → 增量同步
  • 周期快照 → 用加减表求存量,但是要join效率太差,而业务表中sku_num本来就是存量字段,直接全量同步
  • 累积快照 → 多个同一个流程中的业务行为累积到一张表,有多个日期 →

dwd_user_register_inc
dwd_user_login_inc
用户登录和注册都跟页面浏览有关,当离开页面的时候产生日志。页面访问是跟会话相关的,clien连通server端后才是一个会话uuid,超时重连是新的会话,也要重新登陆。
用户注册:一定是在注册页面,成功后携带uid,注册时间取自业务表。
用户登录:同一个会话中uid不为null的时间最早的一条数据即为一次登录。

dwd_trade_trade_flow_acc交易域交易流程累积快照事实表
下单-支付-收货 的平均时间间隔
理论上用uid关联下单表join支付表,求diff,然后直接avg就可以,但是在用户的基础上大表join大表效率差,所以用累计快照表。

4. DWS 预聚合

参考ADS表 指标体系
1d:1天的数据,来自DIM,DWD
nd:N天的数据,来自1d表
td:所有数据,来自DIM,DWD,1d表,每日数据装载避免重复计算可以用历史旧表+今天的新表聚合汇总

5. ADS指标体系

数据域

用户域 → 用户注册、登陆
互动域 → 收藏 评论
工具域 → 优惠券领取、使用
交易域 → 加购、下单、支付、退款
流量域 → UV 跳出会话数 浏览时长

统计指标

原子指标:业务过程 + 度量值 + 聚合逻辑 订单总和
派生指标:原子指标 + 统计周期 + 限定条件 + 统计粒度 最近一天各省份手机品类订单总和
衍生指标:多个派生指标运算 比率

商品主题
最近1/7/30日各品牌下单次数和金额统计
①筛选最近1/7/30的数据union.
②order要joinsku表,sku是全量的,筛选最近几日就会出现几次,join就就有几条,sku因此sku用最近1天的最新的数据即可.
③ads不分区,sql脚本可能会失败重试,所以用overwrite,但是历史数据就会被替代,因此要先查出来再union.

insert overwrite table ads_order_stats_by_cate
select * from  ads_order_stats_by_cate
union
SELECT * from(
select ...from[od1]  --最近1天
union all
select ...from[od7]  --最近7天
union all
select ...from[od30] --最近30天
)t
--1天
SELECT 
       '2022-06-8',
       1,
       count(DISTINCT order_id) order_count,  --按照order_id统计次数
       count(DISTINCT user_id) order_user_count,
       tm_id,
tm_name 
FROM
  (SELECT order_id,
          user_id,
          sku_id
   FROM dwd_trade_order_detail_inc
   WHERE dt='2020-06-08')od   --1
   --WHERE dt>=date_sub('2020-06-08',6) and dt<=’2020-06-08’)od7   --7
   --WHERE dt>=date_sub('2020-06-08',29) and dt<=’2020-06-08’)od7  --30
LEFT JOIN
  (SELECT id,
            tm_id
   FROM dim_sku_full
   WHERE dt='2020-06-8')sku ON od.sku_id=sku.id
GROUP BY tm_id,tm_name;

④避免重复计算,因此提前把一天的数据计算好,7/30的数据从1d的表中取.

--封装一天的表dws_order_status_by_tm_1d  再union
tm_name,
tm_name,
order_count_1d,
ordrr_user_count_1d
partition by('dt' String)

--装载数据
Insert overwrite table dws_order_status_by_tm_1d partition (dt=’2020-06-08’)
SELECT 
       tm_id,
       tm_name,
       count(DISTINCT order_id) order_count_1d,  
       count(DISTINCT user_id) order_user_count_1d,    
FROM
  (SELECT order_id,
          user_id,
          sku_id
   FROM dwd_trade_order_detail_inc
   WHERE dt='2020-06-08')od   --1
LEFT JOIN
  (SELECT id,
          tm_id
   FROM dim_sku_full
   WHERE dt='2020-06-8')sku ON od.sku_id=sku.id
GROUP BY tm_id,tm_name;

⑤避免重复读取:读取次数 io次数,大量小文件,同时打开文件的数量是有限制的,所以读一次,获取时间范围最大的数据集,在内存中炸裂出3份数据,标记,将炸裂后的数据筛选过滤保留有效时间数据,然后根据标记分组统计。

--test
SELECT*
FROM
  (SELECT 'a'
   FROM)t LATERAL VIEW explode(array(1,7,30)) tmp AS tag
WHERE tag!=7;

a 1
a 7
a 30
--最终sql
SELECT 
      '2020-06-08',
       days,
       tm_id,
       tm_name,
       sum(order_count_1d),
       sum(ordrr_user_count_1d)
FROM dws_order_status_by_tm_1d LATERAL VIEW explode(array(1,7,30)) tmp AS days
WHERE dt>=date_sub('2020-06-08',days-1) AND dt<='2020-06-08'
GROUP BY days,tm_id,tm_name;

⑥同理如果有用到7天/30天的数据,也可以将其封装为一张表,只要在这个时间范围内的指定时间的需要都可以查询。

--封装7天的数据表
Insert overwrite table dws_order_status_by_tm_7d partition (dt=’2020-06-08’)
SELECT 
       tm_id,
       tm_name,
       sum(order_count_1d) order_count_7d,
       sum(ordrr_user_count_1d) order_user_count_1d
FROM dws_order_status_by_tm_1d
WHERE dt>=date_sub('2020-06-08',6) AND dt<='2020-06-08'
GROUP BY tm_id, tm_name;

--封装30天的数据表  这里可以直接获取7天的数据,因为重复计算了。
Insert overwrite table dws_order_status_by_tm_30d partition (dt=’2020-06-08’)
SELECT 
       tm_id,
       tm_name,
       sum(order_count_1d) order_count_30d,
       sum(ordrr_user_count_1d) order_user_count_30d
FROM dws_order_status_by_tm_1d
WHERE dt>=date_sub('2020-06-08',29) AND dt<='2020-06-08'
GROUP BY tm_id, tm_name;

SELECT 
       tm_id,
       tm_name,
       sum(if(dt>=date_sub('20220-06-08',6),order_count_7d,0) order_count_7d,
       sum(if(dt>=date_sub('20220-06-08',6),order_user_count_7d,0) order_count_7d,
       sum(ordrr_user_count_1d) order_user_count_7d,
       sum(order_count_1d) order_count_30d,
       sum(ordrr_user_count_1d) order_user_count_30d
FROM dws_order_status_by_tm_1d
WHERE dt>=date_sub('2020-06-08',29) AND dt<='2020-06-08'
GROUP BY tm_id, tm_name;

二、ADS指标


流量主题
渠道流量统计 跳出率=页数浏览数=1的会话/总会话数量 浏览时长
路径分析 桑基图(有向无环) 从哪个页面来 跳到哪个页面 次数

商品主题
复购率
购物车topN
收藏topN

交易主题
下单到支付时间间隔
省份交易统计

用户主题
留存率
行为漏斗分析
连续下单用户
日活、新增活跃
留存率

--1日留存率
SELECT 
    '2022-06-08',  --统计日期
    date_sub('2022-06-08',1),  --用户新增日期
    1, --留存天数
    sum(if(login_date_last='2020-06-08'),1,0) ,--留存用户数据  1天前注册且当天登陆的用户
    count(*),    --新增用户数
    sum(if(login_date_last='2020-06-08'),1,0)  / count(*)  --留存率
FROM dws_user_user_login_td   --历史至今表
WHERE dt = '2022-06-08'
AND login_date_first = date_sub('2022-06-08', -1)   --首次登陆

--2日留存率
SELECT 
    '2022-06-08',  --统计日期
    date_sub('2022-06-08',2),  --用户新增日期
    2, --留存天数
    sum(if(login_date_last='2020-06-08'),1,0) ,--留存用户数据  2天前注册且当天登陆的用户
    count(*),    --新增用户数
    sum(if(login_date_last='2020-06-08'),1,0)  / count(*)  --留存率
FROM dws_user_user_login_td   --历史至今表
WHERE dt = '2022-06-08' 
AND login_date_first <= date_sub('2022-06-08', -2)   --首次登陆

--1-7日留存
insert overwrite table ads_user_retention
select * from ads_user_retention
union
select '2022-06-08' dt,
       login_date_first ,
       datediff('2022-06-08', login_date_first) retention_day,
       sum(if(login_date_last = '2022-06-08', 1, 0)) retention_count,
       count(*) new_user_count,
       cast(sum(if(login_date_last = '2022-06-08', 1, 0)) / count(*) * 100 as decimal(16, 2)) retention_rate
from (
         select user_id,
                login_date_last,
                login_date_first
         from dws_user_user_login_td
         where dt = '2022-06-08'
           and login_date_first >= date_add('2022-06-08', -7)
           and login_date_first < '2022-06-08'
     ) t1
group by login_date_first;  --按照日期分组

用户变动统计

insert overwrite table ads_user_change
select * from ads_user_change
union
select
    churn.dt,
    user_churn_count,
    user_back_count
from
(
    select
        '2022-06-08' dt,
        count(*) user_churn_count
    from dws_user_user_login_td
    where dt='2022-06-08'
    and login_date_last=date_add('2022-06-08',-7)     --末次登陆是7天前
)churn
join
(
    select
        '2022-06-08' dt,
        count(*) user_back_count
    from
    (
        select
            user_id,
            login_date_last
        from dws_user_user_login_td
        where dt='2022-06-08'
        and login_date_last = '2022-06-08'      --当天登陆
    )t1
    join
    (
        select
            user_id,
            login_date_last login_date_previous
        from dws_user_user_login_td
        where dt=date_add('2022-06-08',-1)
    )t2
    on t1.user_id=t2.user_id
    where datediff(login_date_last,login_date_previous)>=8  --且昨天末次登陆是7天前
)back
on churn.dt=back.dt;

7连3

select
    count(distinct(user_id))
from
(
    select
        user_id,
        datediff(lead(dt,2,'9999-12-31') over(partition by user_id order by dt),dt) diff
    from dws_trade_user_order_1d
    where dt>=date_add('2022-06-08',-6)
)t1
where diff<=3;

三、 实时数仓



维表的处理:

离线:主维表和相关维表join每天全量同步一次。
实时:不需要同步历史维表数据。

ADS用户域 :回流/新增/活跃 sum
DWS用户域 :backCt register_ct uuCt

用户登录 - 7日回流 & 当日活跃用户 
dwd_traffic_page  keyby  uid

backCt :lastLoginDt != null && diff >=8天
uuCt :if (lastLoginDt == null || !lastLoginDt.equals(curDt)) 则1,更新dt状态;或者用mid
用户注册 
dwd_user_register  
register_ct:if (createTime != null && id != null) 则1 
windowAll    reduce

ADS流量域 :各渠道流量统计 独立访问数/会话总数/会话浏览页面数/会话停留时长 group by ch sum
DWS流量域 : uvCt/svCt/pvPerSession/durPerSession

各渠道页面浏览  
dwd_traffic_page keyby mid
uvCt:if (lastLoginDt == null || !lastLoginDt.equals(curDt)) 则1,更新dt状态;或者用mid
svCt: last_page_id=null 则1
pvPerSession:来一条数据则1
durPerSession``` 
首页、详情页页面浏览汇总 
page  keyby mid
detailUvCt:1
homeUvCt:if (lastLoginDt == null || !lastLoginDt.equals(curDt)) 则1,更新dt状态;或者用mid

ADS交易域 :
DWS交易域 :

加购独立用户
dwd_trade_cart_add keyby user_id
if (ts != null && userId != null)  
lastLoginDt状态是独立用户则1 
windowAll    reduce
支付成功独立用户 支付成功新用户
dwd_trade_order_payment_success
keyby user_id
SKU粒度下单汇总 / 省份下单次数 金额
order_detail涉及left join回撤流,如果要聚合度量值会重复计算,因为需要按照detail_id去重,只保留最后一条数据,注意回撤流最多只有3条数据
dwd_trade_order_detail   key by order_detail_id

posted @ 2024-05-23 14:30  小花生hadoop  阅读(9)  评论(0编辑  收藏  举报