Sfoglia il codice sorgente

docs(kb): 20 §5.5 改写——事件归 DWD _apd_d、状态归 DIM _zip_d

DWD 事实表只承载不可变事件,实体当前状态进 DIM 拉链表;
循环状态机(如拼团审核反复)走事件流水 + 拉链组合,
天然支持任意次状态变更
tianyu.chu 1 settimana fa
parent
commit
c9b58bab74
1 ha cambiato i file con 26 aggiunte e 20 eliminazioni
  1. 26 20
      kb/20-数仓分层与建模.md

+ 26 - 20
kb/20-数仓分层与建模.md

@@ -132,35 +132,41 @@ RDS PG / ES ──DataX──▶ RAW ──SparkSQL──▶ ODS ──▶ DWD 
 
 > **关键映射**:业务过程 → DWD 事实表;度量 → 事实字段;维度 → DIM 表;派生指标 = 原子指标 + 时间周期 + 修饰词 → DWS/ADS 字段。
 
-### 5.5 DWD 明细粒度设计(合 vs 拆
+### 5.5 DWD 事实表设计(事件 vs 状态
 
-**核心规则:粒度相同则合,粒度不同则拆。**
+**核心规则:DWD 事实表只承载业务事件(不可变事实),实体当前状态进 DIM 拉链表(`_zip_d`)**。
 
-**判断"粒度是否相同"的一个问题**:两个业务动作描述的是不是同一个实体的同一条记录?
+**事件 vs 状态判别:**
 
-- 同一个实体 + 同一条记录(只是状态/阶段不同)→ 合
-- 同一个实体 + 但每次动作产生新记录 → 拆
-- 不同实体 → 拆
+- **事件**:某一时刻发生的业务动作(下单、支付、发货、签收、提交审核、审核通过 / 拒绝),不可变,写入即固定
+- **状态**:实体当前的属性或阶段(订单当前状态、用户等级、拼团当前状态),随时间演化,可变
 
-**典型场景:**
+**建模规则:**
+
+| 类型 | 归属 | 命名(参考 `21-命名规范.md`)| 示例 |
+|---|---|---|---|
+| 业务事件 | DWD 事实表,每个业务过程一张 | `dwd_{域}_{业务过程}_apd_d` | `dwd_trd_order_create_apd_d`、`dwd_trd_order_pay_apd_d` |
+| 实体状态 | DIM 拉链表 | `dim_{域}_{实体}_zip_d` | `dim_trd_order_zip_d`(含 `current_status` / 各阶段时间 / `start_date` / `end_date`) |
 
-| 场景 | 粒度关系 | 结论 | 理由 |
-|------|---------|------|------|
-| 拼团发起 vs 拼团成功 | 同一个团的状态变化 | 合 | 粒度都是团 ID,用 status + 多个时间列区分 |
-| 下单 vs 支付 vs 发货 vs 签收 | 同一个订单的生命周期 | 合 | 粒度都是订单 ID,每个阶段一列时间戳(对应 `21-命名规范.md` §2.2 `acc` 快照) |
-| 拼团 vs 订单 | 一对多 | 拆 | 团是团、单是单,粒度不同 |
-| 订单 vs 订单明细行 | 一对多(一个订单多个商品) | 拆 | 行项目有独立粒度 |
-| 浏览 vs 点击 vs 加购 | 各自独立事件流 | 拆 | 每次行为是独立记录(对应 `21-命名规范.md` §2.2 `apd` 快照) |
+**为什么这么拆**:
 
-**自检方法:**
+- DWD `_apd_d` 只追加,与 Hive 列存(ORC)"只追加 + 分区"模型天然契合,不需要 UPDATE
+- 状态查询("截止 dt 各状态订单数")走拉链表 `is_current` 切片或按 dt 切片,直观
+- 跨表 JOIN("下单数 - 退单数 = 实际下单数")是维度建模的**正常能力**,不是代价
+- 这是 Hive / 列存数仓的事实标准做法(阿里 OneData / 字节 / 美团数仓主流)
+
+**典型场景:**
 
-1. **能不能自然地用一行表示?** 如果合并后一行就是一个完整业务实体(一个团、一个订单),各阶段只是多几列时间/状态字段 → 合
-2. **合并后会不会出现一对多导致行膨胀?** 如果合并后同一个 ID 会出现多行 → 说明粒度不同,必须拆
-3. **分析时是否总要 JOIN 回来?** 如果拆了之后大多数分析场景都要把两张表 JOIN 在一起 → 说明本来就该合
+| 场景 | 事件表(DWD `_apd_d`) | 状态表(DIM `_zip_d`) |
+|------|----------------------|----------------------|
+| 订单履约(下单 → 支付 → 发货 → 签收,单向) | 4 张 `_apd_d` | `dim_trd_order_zip_d` |
+| 拼团(发起 → 审核 → 审核拒绝 → 重新提交 → ……,循环) | 每个动作一张 `_apd_d`(含"审核拒绝"、"重新提交"等可重复事件) | `dim_trd_group_zip_d`,每次状态变更生成新拉链行 |
+| 浏览 / 点击 / 加购(独立事件流,无状态实体) | 各一张 `_apd_d` | 无 |
+| 订单 vs 订单明细行(一对多) | 各一张 `_apd_d`(粒度不同必拆) | 无 |
 
-**一句话总结**:一行能完整描述一个业务实体就合,合了之后一个 ID 会出现多行就拆。
+**循环状态机的处理**:业务流程若存在循环(如拼团审核可能多次反复:"提交 → 拒绝 → 修改 → 再提交"),事件流水(`_apd_d`)天然支持任意次重复——每次状态变更追加一行,完整还原过程;DIM 拉链表(SCD Type 2)每次状态变更生成新行,旧行 `end_date` 置变更前一天,状态历史完整可回溯
 
-**状态时间字段的设计**:保留 3-5 个核心状态的时间字段(值为时间戳),其他非核心状态不建新列,使用原始状态码标记当前状态。后期若需要状态流水分析,新建状态流水表(`_apd_d`)而不是改主表
+**自检**:建表想给字段加"时间戳 + 若干状态字段"时,停一步问:这是事件还是状态?事件 → `_apd_d`;状态 → `_zip_d`
 
 ## 6. 数据同步策略