外观
表粒度校验怎么做
难度与前置
难度:进阶
前置:会写分组聚合 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. 自动化门禁(调度后置)
- 唯一键重复行 = 0(阻断)
- 行数相对基线波动超阈(告警,见 波动基线)
- 与上游 key 集合差集抽样人工认领
- 回刷任务复用同一套 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,保留只读至约定日落日
常见坑(至少牢记这些)
COMMENT 写「订单表」却不写 grain
三个开发三种理解:头、行、支付成功单。必须写「一行=…」。用 COUNT(*) 当唯一性
无重复键时 COUNT(*) 仍可能含脏字段;唯一性看逻辑键。明细挂头字段导致 SUM 翻倍
金额、运费在头上,Join 明细后勿再 SUM 头字段。拉链/快照混用同一验收 SQL
拉链按entity_id + start_dt;快照按entity_id + dt。套错必误报。测试分区跳过 grain 门禁
一上生产全是重复。门禁对 prod 强制,dev 可告警不阻断但要可见。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 = 0bash
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 原创数仓/面试文,已改写成可执行清单;未包含未公开公司数据与内部表名。