Skip to content

表粒度校验怎么做 ​

难度与前置

难度:进阶
前置:会写分组聚合 SQL;清楚「一行代表什么业务实体/事件」。
读完能做什么:为每张表声明 grain,用可重复 SQL 验收唯一性与层间对齐,避免指标翻倍、Join 膨胀。

适用场景 ​

  • 新建 DWD/DWS/ADS 表,评审要回答「一行是什么」
  • 指标对不上:怀疑重复行、粒度混用(订单行 vs 订单头)
  • Join 后行数暴涨,或 SUM 翻倍
  • 回刷后行数异常,需快速定位是否 grain 被破坏

不适用:

  • 纯探查型临时表(仍建议临时声明 grain)
  • 无业务语义的日志原文堆放(ODS 可保留多行,但下游必须先定 grain)
  • 已有完备 DQ 且 grain 规则已入库——本文用于补规则与验收模板

真实场景(一条) ​

订单宽表声称「一行一订单」,实际混入了订单明细(一行一 SKU)。ADS gmv 按订单汇总时被明细行放大 3~8 倍。治理:表注释与元数据写明 grain = order_id;DWD 拆成订单头 / 明细两表;上线前跑唯一键与行数对账。GMV 与财务对账误差从 40%+ 回到声明阈值内。

怎么做(步骤清单) ​

1. 先用一句话写清 grain ​

模板:

text
表 <db.table> 的 grain =
  <实体或事件> + <时间/版本维度(若有)>
示例:
- 一行 = 一个 order_id 在业务日 dt 的订单头快照
- 一行 = 一个 user_id × sku_id × dt 的加购事件(可重复需事件 id)

写入:表 COMMENT、设计文档、元数据「粒度」字段。评审不通过「感觉一行差不多」。

2. 声明唯一键(逻辑 PK)与可选业务键 ​

层级典型 grain逻辑唯一键示例
ODS 明细源端一行/一条消息源 PK 或 msg_id
DWD 事实业务事件一次发生event_id / order_id
DWD 明细订单行order_id + sku_id + line_no
快照维实体当日状态entity_id + dt
拉链维实体有效区间entity_id + start_dt(闭开区间约定)
DWS实体 × 统计周期user_id + dt

与 主键/唯一键设计 对齐:逻辑键进 DQ,物理是否建约束看引擎能力。

3. 唯一性验收 SQL(必跑) ​

sql
-- 1) 重复键计数:应为 0
SELECT order_id, COUNT(*) AS cnt
FROM dwd_order_header_di
WHERE dt = '${biz_date}'
GROUP BY order_id
HAVING COUNT(*) > 1
LIMIT 100;

-- 2) 重复占比
SELECT
  COUNT(*) AS row_cnt,
  COUNT(DISTINCT order_id) AS distinct_key_cnt,
  COUNT(*) - COUNT(DISTINCT order_id) AS dup_row_est
FROM dwd_order_header_di
WHERE dt = '${biz_date}';

阈值:生产日分区 dup_row_est = 0;若允许「同一键多版本」,grain 必须改成带版本/时间的复合键。

4. 行数与上游对齐(防丢、防胀) ​

sql
-- 源/上一层 vs 本层(按 grain 去重后比)
SELECT
  'src' AS side, COUNT(DISTINCT order_id) AS key_cnt
FROM ods_order_delta WHERE dt = '${biz_date}'
UNION ALL
SELECT
  'dwd', COUNT(DISTINCT order_id)
FROM dwd_order_header_di WHERE dt = '${biz_date}';

规则:

  • 头表:key 数 ≈ 源订单数(排除声明过滤)
  • 明细表:行数 ≥ 头表,且 order_id 集合 ⊆ 头表
  • 差异必须能归因(取消单、测试单、延迟到达)

5. Join 前 grain 兼容性检查 ​

text
兼容:订单头 (order_id) ⋈ 用户维 (user_id) —— 多对一,事实行数不变
危险:订单头 ⋈ 订单明细 —— 一对多,SUM(订单金额) 会重复
危险:两张同 grain 事实直接 FULL JOIN 未去重
sql
-- Join 后行数应 ≈ 驱动表行数(多对一时)
SELECT
  (SELECT COUNT(*) FROM dwd_order_header_di WHERE dt = '${biz_date}') AS fact_cnt,
  COUNT(*) AS joined_cnt
FROM dwd_order_header_di f
LEFT JOIN dim_user_df u
  ON f.user_id = u.user_id AND u.dt = '${biz_date}'
WHERE f.dt = '${biz_date}';

joined_cnt 明显高于 fact_cnt → 维表 grain 或关联键有问题。

6. 指标层强制「先聚合到目标 grain」 ​

sql
-- 坏:明细直接 SUM 订单应付金额字段(若金额在头上重复挂明细)
-- 好:先到订单 grain 再汇总
SELECT
  dt,
  SUM(pay_amt) AS gmv
FROM (
  SELECT dt, order_id, MAX(pay_amt) AS pay_amt  -- 或从订单头取
  FROM dwd_order_item_di
  WHERE dt = '${biz_date}'
  GROUP BY dt, order_id
) t
GROUP BY dt;

ADS 文档写明:指标从哪张表、何种 grain 汇总而来。参见 指标口径一致性。

7. 自动化门禁(调度后置) ​

  1. 唯一键重复行 = 0(阻断)
  2. 行数相对基线波动超阈(告警,见 波动基线)
  3. 与上游 key 集合差集抽样人工认领
  4. 回刷任务复用同一套 grain SQL
bash
# 示意:质量任务入口
# dq_run --table dwd_order_header_di --rule grain_unique --dt ${biz_date}
echo "rule=grain_unique,table=dwd_order_header_di,dt=${biz_date},status=PASS"

8. 变更 grain 的发布流程 ​

  • 改 grain = 破坏性变更:新表或新版本字段,禁止静默改语义
  • 下游清单、回刷窗口、双写对比、公告
  • 旧表标记 deprecated,保留只读至约定日落日

常见坑(至少牢记这些) ​

  1. COMMENT 写「订单表」却不写 grain
    三个开发三种理解:头、行、支付成功单。必须写「一行=…」。

  2. 用 COUNT(*) 当唯一性
    无重复键时 COUNT(*) 仍可能含脏字段;唯一性看逻辑键。

  3. 明细挂头字段导致 SUM 翻倍
    金额、运费在头上,Join 明细后勿再 SUM 头字段。

  4. 拉链/快照混用同一验收 SQL
    拉链按 entity_id + start_dt;快照按 entity_id + dt。套错必误报。

  5. 测试分区跳过 grain 门禁
    一上生产全是重复。门禁对 prod 强制,dev 可告警不阻断但要可见。

  6. CDC 回放制造同 PK 多版本却当快照 grain
    快照需「取最新」或「按版本键」;否则唯一性失败。

如何验收(可量化) ​

  • [ ] 元数据/表注释有明确 grain 一句话
  • [ ] 逻辑唯一键日分区重复行 = 0(或差异有工单)
  • [ ] 与上游 key 对齐差集可解释
  • [ ] 关键事实表的 1 对 1/多对 1 Join 行数不膨胀
  • [ ] ADS 指标能追溯到源表 grain
  • [ ] 回刷后重跑同一套 SQL 仍通过
sql
-- 验收打包示例
WITH u AS (
  SELECT order_id, COUNT(*) c
  FROM dwd_order_header_di WHERE dt = '${biz_date}'
  GROUP BY order_id
)
SELECT
  SUM(CASE WHEN c > 1 THEN 1 ELSE 0 END) AS dup_keys,
  COUNT(*) AS key_cnt
FROM u;
-- 期望:dup_keys = 0
bash
echo "biz_date,table,dup_keys,row_cnt,upstream_key_cnt,status"
# 2026-09-25,dwd_order_header_di,0,1254301,1254301,PASS

相关面试题 ​

占位:与题库小程序对齐后补链接(建议关联:表粒度、主键唯一性、Join 膨胀、指标翻倍排查)。


改写说明:结构与判断标准参考 Wood 原创数仓/面试文,已改写成可执行清单;未包含未公开公司数据与内部表名。