4. DWD层(明细数据层):从杂乱到规整的第一道关卡

DWD层,全称是Data Warehouse Detail,明细数据层。很多刚入行的朋友会把它简单理解成「把ODS层的数据搬过来,稍微洗一洗」。说实话,我早年也这么干过,结果后面做汇总层的时候,各种数据对不上,排查问题查到怀疑人生。

DWD层的核心使命,就是做一件事:把业务数据变成分析可用的数据。它不负责汇总,不负责宽表,它只负责把数据洗干净、标准化、拆解好。你想想看,如果源头的数据都是脏的、乱的、口径不统一的,上层再怎么建模都是白搭。

DWD层的核心定位: 业务数据与分析模型之间的「翻译层」。它把OLTP系统的操作型数据,翻译成OLAP系统需要的分析型数据。

4.1 DWD层的定位与作用

我个人的习惯是,把DWD层看作数据仓库的「地基」。地基打不牢,楼盖得再高也得塌。具体来说,DWD层承担了三个关键角色:

  • 数据清洗与标准化:去掉脏数据,统一编码、单位、时区、格式
  • 业务过程建模:把业务事件拆解成原子粒度的事实和维度
  • 维度退化:把冗余的描述信息从事实表中剥离,或者直接退化到事实表

我记得有一次做电商项目,ODS层直接同步了MySQL的订单表,里面有个字段叫order_status,值有'0'、'1'、'2'、'3',还有'待支付'、'已发货'、'已完成'。同一个字段,不同时期的数据,编码规则都不一样。这种数据如果不做清洗和标准化,直接拿去分析,结果可想而知。

注意: DWD层的数据粒度必须与ODS层保持一致,或者更细。不要在这里做任何聚合操作,否则会丢失明细信息,后续无法回溯。

4.2 数据清洗与标准化

数据清洗,说白了就是「去伪存真」。标准化,就是「统一口径」。这两件事在DWD层必须做彻底。

我一般把清洗规则分成四类:

清洗类型 常见问题 处理方式
完整性清洗 空值、缺失值 填充默认值、剔除、标记异常
准确性清洗 数据格式错误、越界值 格式校验、范围校验、规则修正
一致性清洗 编码不统一、单位不统一 映射转换、单位换算、时区对齐
时效性清洗 重复数据、过期数据 去重、时间戳过滤、版本管理

举个例子,订单金额的单位问题。有的系统存的是「分」,有的存的是「元」。如果不统一,汇总的时候就会差100倍。我建议在DWD层统一转换成「元」,保留两位小数,并且字段名加个注释-- 单位:元,方便后面的人理解。

小技巧: 清洗逻辑不要写在ETL脚本里,要写成独立的清洗函数或存储过程。这样当上游数据源变更时,只需要改清洗函数,不用动整个ETL流程。

4.3 维度退化

维度退化这个概念,很多人第一次听会觉得有点绕。我换个说法:把维度表中那些「一次性」的描述信息,直接塞回事实表里。

为什么会这样?你想想看,有些维度属性,比如订单的「支付方式」,它只跟这个订单本身有关,不会跟其他订单共享。如果单独建一张支付方式维度表,里面可能就两三条记录(支付宝、、银行卡),查询的时候还要多一次JOIN,得不偿失。

所以,我个人的做法是:

  • 高基数、低共享的维度属性:直接退化到事实表,比如订单号、支付流水号、设备ID
  • 低基数、高共享的维度属性:保留维度表,比如商品分类、地区、时间
  • 中间情况:看查询频率,如果90%的查询都要用到这个属性,就退化

我曾经在一个金融项目中,把「交易渠道」这个字段退化到了事实表。原本设计了一张渠道维度表,有20多个字段。后来发现,每次分析交易数据都要关联这张表,查询性能很差。退化之后,事实表只保留了一个渠道编码字段,查询速度提升了3倍多。

维度退化的核心原则: 减少JOIN,提升查询性能。但不要过度退化,否则事实表会变得臃肿不堪。

4.4 事实表设计

事实表是DWD层的核心产出。三种类型的事实表,各有各的适用场景。我一个个说。

4.4.1 事务事实表

事务事实表,记录的是「每一笔业务事件」。比如每一笔订单、每一次登录、每一次点击。它的特点是:一行代表一个事件,一旦写入就不会修改。

适用场景:

  • 需要分析单个事件的细节,比如订单金额、支付时间
  • 需要统计事件发生的次数,比如日活、订单量
  • 需要做事件间的关联分析,比如下单到支付的转化

设计要点:

  • 粒度必须最细,不要合并同类事件
  • 外键尽量使用代理键,不要直接用业务主键
  • 度量字段必须是可加的,比如金额、数量
-- 事务事实表示例:订单事实表
CREATE TABLE dwd_order_fact (
    order_sk      BIGINT,       -- 代理键
    order_id      VARCHAR(32),  -- 退化维度:订单号
    user_sk       BIGINT,       -- 用户维度外键
    product_sk    BIGINT,       -- 商品维度外键
    store_sk      BIGINT,       -- 门店维度外键
    order_amount  DECIMAL(10,2),-- 订单金额(元)
    order_qty     INT,          -- 订单数量
    order_time    TIMESTAMP,    -- 下单时间
    pay_time      TIMESTAMP,    -- 支付时间
    -- 其他退化维度
    pay_channel   VARCHAR(16),  -- 支付渠道
    order_status  VARCHAR(8)    -- 订单状态
);

4.4.2 周期快照事实表

周期快照事实表,记录的是「某个时间点的状态」。比如每天的用户余额、每月的库存量。它的特点是:按固定周期(每天、每月)记录快照,即使数据没变也会生成一条记录。

适用场景:

  • 需要分析某个时间点的存量数据,比如账户余额、库存水位
  • 需要做趋势分析,比如连续30天的用户活跃度变化
  • 事务事实表无法直接计算的累积指标,比如月活跃用户数

设计要点:

  • 必须包含快照时间字段,通常是日期维度外键
  • 度量字段通常是半可加的,比如余额不能跨时间相加
  • 数据量会随时间线性增长,需要做好分区策略
-- 周期快照事实表示例:每日用户余额快照
CREATE TABLE dwd_user_balance_snapshot (
    snapshot_date DATE,         -- 快照日期
    user_sk       BIGINT,       -- 用户维度外键
    balance       DECIMAL(10,2),-- 当日余额
    frozen_amount DECIMAL(10,2),-- 冻结金额
    total_deposit DECIMAL(10,2),-- 累计充值(截至当日)
    update_time   TIMESTAMP     -- 最后更新时间
);

经验之谈: 周期快照事实表的数据量通常很大,建议按日期做分区,并且只保留最近N个月的数据。历史数据可以归档到冷存储。

4.4.3 累积快照事实表

累积快照事实表,记录的是「一个业务流程从开始到结束的完整过程」。比如一笔订单从下单、支付、发货到签收的全生命周期。它的特点是:一行代表一个业务流程实例,随着流程推进不断更新。

适用场景:

  • 需要分析业务流程的时长,比如从下单到签收的平均时间
  • 需要追踪业务流程的进度,比如当前有多少订单在「已发货」状态
  • 需要做漏斗分析,比如从下单到支付的转化率

设计要点:

  • 包含多个时间戳字段,记录每个关键节点的时间
  • 包含状态字段,记录当前所处的流程阶段
  • 数据行会更新,直到流程结束
-- 累积快照事实表示例:订单生命周期事实表
CREATE TABLE dwd_order_lifecycle (
    order_sk        BIGINT,       -- 代理键
    order_id        VARCHAR(32),  -- 订单号
    create_time     TIMESTAMP,    -- 下单时间
    pay_time        TIMESTAMP,    -- 支付时间
    ship_time       TIMESTAMP,    -- 发货时间
    receive_time    TIMESTAMP,    -- 签收时间
    cancel_time     TIMESTAMP,    -- 取消时间(如果有)
    current_status  VARCHAR(16),  -- 当前状态:已下单/已支付/已发货/已签收/已取消
    total_duration  INT,          -- 总耗时(分钟),流程结束后计算
    -- 维度外键
    user_sk         BIGINT,
    product_sk      BIGINT,
    store_sk        BIGINT
);

注意: 累积快照事实表是「可变」的,ETL过程中需要做更新操作。如果数据量很大,更新性能会成为瓶颈。建议配合拉链表或增量更新策略使用。

4.5 三种事实表对比

为了方便你理解,我把三种事实表的差异整理成了一张表:

对比维度 事务事实表 周期快照事实表 累积快照事实表
记录内容 单笔事件 时间点状态 流程全貌
数据更新 只追加,不更新 追加新快照 更新已有行
粒度 每笔事件一行 每个周期每个实体一行 每个流程实例一行
典型场景 订单、点击、支付 余额、库存、日活 订单生命周期、工单流程
度量可加性 完全可加 半可加(跨时间不可加) 半可加(跨实例不可加)

4.6 DWD层核心知识体系

下面这张图,是我梳理的DWD层核心知识结构。你可以把它当作一个检查清单,看看自己的DWD层设计是否覆盖了这些要点。

DWD层核心知识体系 DWD层核心知识体系 DWD层(明细数据层) 定位与作用 数据清洗与标准化 业务过程建模 维度退化 数据清洗与标准化 完整性清洗(空值/缺失) 准确性清洗(格式/越界) 一致性清洗(编码/单位) 时效性清洗(去重/过期) 维度退化 高基数低共享 → 退化 低基数高共享 → 保留维度表 减少JOIN,提升查询性能 事实表设计 事务事实表(单笔事件) 周期快照事实表(时间点状态) 累积快照事实表(流程全貌) 设计原则总结 1. 保持原子粒度,不做聚合 2. 清洗规则标准化,可复用 3. 维度退化要适度,平衡查询性能与模型灵活性 4. 三种事实表按场景选择,可混合使用

嗯,DWD层的内容就讲到这里。说白了,它就是数据仓库里最「脏」最「累」的活,但也是最有价值的一层。把DWD层做好了,后面的DWS、ADS层就是水到渠成的事。