数仓分层别背定义:用一笔订单讲清 ODS、DWD、DWS、ADS
写这篇东西,是因为分层这四个缩写,我自己不太清楚,所以呢干脆写一篇文章来彻底分析下。
ODS 是贴源、DWD 是明细、DWS 是汇总、ADS 是应用------这话没错,也没用。新人真正卡住的是:这张表到底该落哪一层?口径改了改哪张?报表能不能直接扫 ODS?DWS 和 ADS 长得很像的时候,凭什么还要拆?
我直接给结论,后面用一笔真实风格的订单把四层走一遍。看完你应该能自己划表,而不是再去搜「ODS 是什么意思」。
- ODS:业务库当天长什么样,仓里就原样留一份。不对齐口径,不发明指标。
- DWD:把「一笔业务事实」洗成数仓自己的语言。单位、状态、主键、关联键在这里定死。
- DWS:按分析常用的粒度预先加总。人还没提问,数已经按天、按店、按品类滚好了。
- ADS:某一类人、某一个应用今天要看的那张表。允许冗余,不允许再改口径。
DIM(维度)不是第五个「事实层」,是给 DWD / DWS 提供「商品叫什么、门店属哪个区」的查找表。本文案例里会用到,但不跟四层抢戏。
目录
- 为什么要分层,不分层会死在哪
- 四层各自保什么,别混
- 案例主线:连锁门店的一笔订单
- ODS:源库搬进来,动字段就输了
- DWD:口径只在这里定一次
- DWS:按人会问的粒度先加好
- ADS:给报表、API、问数的那一张
- 再补一个投放 ROI,看四层怎么接力
- 一张表该进哪层:我用的判断方法
- 常见做错的拆法
- 和调度、质量怎么配合
1. 为什么要分层,不分层会死在哪
数仓分层不是阿里发明的仪式,是被重复劳动逼出来的。
没分层时,我见过三种死法,三种都真实发生过:
第一种:每个报表自己从业务库抽。 经营周报一套 SQL,老板看板又一套,AI 问数再写一套。某天产品把「已支付」状态从 3 改成 30,三套 SQL 改了两套,第三套安静地少算了 17% 的 GMV。对账对了两天,最后发现不是丢数,是漏改。
第二种:一张「超级宽表」打天下。 订单、用户、商品、投放、退款全 JOIN 进一张 200 列的表,谁要数都从这张出。结果是:改一个投放口径,订单链路要重跑;查个门店 GMV 扫两亿行;没人敢 DROP 任何一列。
第三种:明细和指标混在一张表里。 同一张表既有 pay_amount 又有 gmv_7d 又有 wow_rate。重跑某一天明细,周同比字段还是旧的;只重跑指标,明细又对不上。你根本分不清该重跑哪一段。
分层要解决的就三件事:
- 口径有且只有一处落地(DWD / 指标字典),上面的层只引用,不重新解释「什么叫支付成功」。
- 重跑有边界。源库抽错了重跑 ODS;状态枚举漏了重跑 DWD;看板多一个「客单价」只动 ADS。
- 算力花在该花的地方。明细扫描一次,汇总结果被 20 张报表复用。别让 20 张报表各自扫一遍订单明细。
层多了也有代价:任务依赖变长,数据晚出,表变多。所以我主张五层封顶(四层事实 + DIM),团队小于 5 个数据开发,别再拆 STG、MID、CWD 那种只有咨询公司幻灯片才出现的层。
2. 四层各自保什么,别混
先把不变量说清楚。每一层对外承诺的东西不一样,混了就等于没层。
| 层 | 一行代表什么 | 允许做什么 | 禁止做什么 |
|---|---|---|---|
| ODS | 源表的一条记录(或一天的一份快照) | 加分区、加 etl 时间、必要时加源表名 | 改金额单位、改状态含义、算 GMV |
| DWD | 一笔对得上业务过程的事实(一单、一行商品、一次退款) | 清洗、去重、统一编码、关联维度键、打业务旗标 | 按门店汇总、算同比、为某个报表特制字段 |
| DWS | 某个粒度上的一段统计(某店某天、某品类某天) | GROUP BY、可加指标、轻度跨域关联 | 再发明「支付成功」的定义;堆只有一张看板用的字段 |
| ADS | 某个应用的一次读取形状 | 冗余、预计算同比、拼看板要的宽表 | 自己扫 ODS 重算口径;把财务没签过的公式藏在这里 |
一句话对照:
- ODS 对源系统负责,对得上源库,才叫贴源。
- DWD 对业务过程负责,对得上「这一单到底算不算卖出去」。
- DWS 对分析粒度负责,对得上「按店按天能不能加总还原」。
- ADS 对使用方负责,对得上「这张接口 / 这张图今天要哪些列」。
数据流向只能单向:
业务库 / 文件 → ODS → DWD(+ DIM) → DWS → ADS → BI / API / Agent
允许跳层读吗?分析师偶尔从 DWD 下钻,可以。调度任务从 ODS 直接出 ADS,不行。跳层写任务,口径一定会被抄歪。
3. 案例主线:连锁门店的一笔订单
后面所有表,都围绕同一天、同一单。数字是编的,结构是我在经营分析里真实用的。
场景:连锁零售(门店 + 小程序)。2026-08-22,用户 U1001 在 SHOP_08(徐汇店)买了两样东西,微信支付成功;当天晚上其中一件申请退款,23 号退款完成。
业务库里实际长这样(MySQL,字段名很脏,这才像真的):
t_order(订单头)
| id | user_id | shop_id | status | pay_amt | pay_type | ctime | mtime | del_flag |
|---|---|---|---|---|---|---|---|---|
| 8001001 | 1001 | 8 | 3 | 19900 | 2 | 2026-08-22 10:03:11 | 2026-08-22 10:05:02 | 0 |
t_order_item(订单行)
| id | oid | sku | num | pay | cost | ctime |
|---|---|---|---|---|---|---|
| 1 | 8001001 | A01 | 1 | 12900 | 7000 | 2026-08-22 10:03:11 |
| 2 | 8001001 | B02 | 2 | 7000 | 3000 | 2026-08-22 10:03:11 |
t_refund(退款,23 号才有)
| id | oid | sku | refund_amt | status | finish_time |
|---|---|---|---|---|---|
| 9001 | 8001001 | B02 | 7000 | 2 | 2026-08-23 19:12:08 |
t_sku
| sku | name | cat1 | cat1_name |
|---|---|---|---|
| A01 | 滤芯套装 | 12 | 配件 |
| B02 | 清洁剂*2 | 12 | 配件 |
几个「业务库特有的恶心处」,后面分层就是来消化它们的:
- 金额单位是 分,不是元。
shop_id = 8,不是SHOP_08;用户是1001,不是U1001。status = 3表示已支付,码表在另一张t_dict里,注释还是三年前的。- 订单头有总金额,明细行也有金额,两边理论上相等,线上偶尔不相等。
- 退款是另一张表、另一个业务日期。如果你在订单表上直接减,22 号的 GMV 会在 23 号被改掉------经营日报就没法做同比了。
运营问的是三句人话:
- 昨天徐汇店卖了多少?(要的是 22 号支付 GMV)
- 净收多少?(支付减退款,退款记在 退款完成日)
- 老板看板只要一行:全网 GMV、毛利、客单、净 GMV。
这三句话对应三层加工,不是对应三套从 MySQL 开查的 SQL。
4. ODS:源库搬进来,动字段就输了
ODS(Operational Data Store)在数仓里的意思很窄:贴源层。你不是在重建业务库,你是在给业务库拍一张按天归档的照片。
4.1 这一单进 ODS 长什么样
两张增量贴源表,分区 ds=20260822。金额仍是分,状态仍是 3,店铺仍是 8。
ods.ods_trd_order_di
| order_id | user_id | shop_id | order_status | pay_amount | pay_type | gmt_create | gmt_modified | is_deleted | etl_time | ds |
|---|---|---|---|---|---|---|---|---|---|---|
| 8001001 | 1001 | 8 | 3 | 19900 | 2 | 2026-08-22 10:03:11 | 2026-08-22 10:05:02 | 0 | 2026-08-23 01:40:00 | 20260822 |
ods.ods_trd_order_item_di 两行,pay 仍是 12900 / 7000。商品维表走日全量 ods.ods_itm_sku_df,也是源字段原样。
技术字段可以加:etl_time、ds。有人会加 src_table,多数据源时有用。除此之外,不要把 19900 改成 199.00。
4.2 ODS 为什么必须「脏」
因为对账和对源。DWD 把单位换成元之后,财务说差 1 分,你要能回到 ODS 指着源库字段说:仓没算错,源就是这个数。ODS 一清洗,这条后路就断了。
第二件事是重跑。源库 mtime 增量漏了一小时,你只重抽 ODS 当天分区,下游 DWD 再跑。如果 ODS 里已经把状态 3「翻译」成 PAID,源库码表改了你还得改 ODS 任务------贴源层被你做成了业务层。
4.3 增量还是全量
| 源表特征 | ODS 策略 | 本案例 |
|---|---|---|
有可靠 mtime、量大、少物理删 |
日增量 _di |
订单、订单行、退款 |
| 量小、会改历史行、要看某天当时的样子 | 日全量 _df |
商品、门店 |
没有 mtime、还有物理 DELETE |
日全量,或上 Binlog | 别硬做增量 |
增量分区里的「ds」是 业务日期(调度的 ${bizdate}) ,不是「记录创建日期」。22 号 10 点下的单,23 号才改状态,增量会进 ds=20260823 的 ODS 分区。DWD 要按 pay_time 落到支付日,这是下一层的事,ODS 不要擅自把这条记录改写到 22 号去。
4.4 ODS 不回答经营问题
你在 ODS 上 SUM(pay_amount) 得到 19900。这是分,还含不含未支付,你不敢对外说。所以:ODS 可以抽查、可以对源,不可以直接给老板。
5. DWD:口径只在这里定一次
DWD(Data Warehouse Detail)是明细事实层。一行还是一笔业务,但字段已经是数仓的普通话。
本案例拆三张事实,不要揉一张:
dwd_trd_order_di:支付成功的销售事实,粒度到 SKU 行dwd_trd_refund_di:退款完成事实dim_itm_sku_df/dim_chn_shop_df:维度日快照
5.1 销售事实:这一行在 DWD 里变成什么
dwd.dwd_trd_order_di,ds=20260822(按 支付成功日 分区)
| order_id | order_item_id | user_id | shop_id | sku_id | cate_id_l1 | cate_name_l1 | pay_time | sku_qty | pay_amount | cost_amount | gross_profit | is_valid_pay | ds |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 8001001 | 1 | U1001 | SHOP_08 | A01 | 12 | 配件 | 2026-08-22 10:05:02 | 1 | 129.00 | 70.00 | 59.00 | 1 | 20260822 |
| 8001001 | 2 | U1001 | SHOP_08 | B02 | 12 | 配件 | 2026-08-22 10:05:02 | 2 | 70.00 | 30.00 | 40.00 | 1 | 20260822 |
相对 ODS,这里干了这些活------也 只允许 在这里干:
- 分改元:
pay_amount / 100。 - ID 统一:
8 → SHOP_08,1001 → U1001,和维度表、其他数据域对齐。 - 状态翻译:
status=3且已支付、未关闭 →is_valid_pay=1。枚举写进注释,和产品、财务对过字。 - 关联维度:品类从 DIM 取,不从订单行「顺便存个名字」各写各的。
- 头行核对:头表 199.00 对明细 129+70。对不上的打
is_amount_mismatch=1,进异常表或质量告警,不要静默以头表为准或以明细为准。选哪边当真相,要明文规定。我一般以明细行为销售事实,头表只做核对。 - 毛利:
pay_amount - cost_amount。成本口径跟财务签过才能进 DWD。没签就先留cost_amount,别在 SQL 里偷偷乘一个「经验毛利率」。
退款 不进这张表 。22 号这两行永远是支付事实。23 号退 70 元,写 dwd_trd_refund_di 的 ds=20260823。
这是分层里最反直觉、也最值钱的一点:事实按业务过程落分区,不按「最后一次修改」回写历史。 否则昨天的经营日报今天打开会变,同比全废。
5.2 DWD 的 SQL 长什么样(骨架)
sql
INSERT OVERWRITE TABLE dwd.dwd_trd_order_di PARTITION (ds = '${bizdate}')
SELECT
CAST(o.order_id AS STRING) AS order_id,
CAST(i.id AS STRING) AS order_item_id,
CONCAT('U', o.user_id) AS user_id,
sh.shop_id AS shop_id,
i.sku AS sku_id,
sku.cate_id_l1,
sku.cate_name_l1,
o.gmt_modified AS pay_time, -- 实际应按支付时间字段,这里示意
i.num AS sku_qty,
CAST(i.pay AS DECIMAL(18,2)) / 100 AS pay_amount,
CAST(i.cost AS DECIMAL(18,2)) / 100 AS cost_amount,
CAST(i.pay - i.cost AS DECIMAL(18,2)) / 100 AS gross_profit,
CASE
WHEN o.order_status IN ('3', '30') -- 码表变更时只改这里
AND NVL(o.is_deleted, 0) = 0
THEN 1 ELSE 0
END AS is_valid_pay
FROM ods.ods_trd_order_di o
JOIN ods.ods_trd_order_item_di i
ON o.order_id = i.oid AND i.ds = '${bizdate}'
LEFT JOIN dim.dim_chn_shop_df sh
ON CAST(o.shop_id AS STRING) = sh.src_shop_id AND sh.ds = '${bizdate}'
LEFT JOIN dim.dim_itm_sku_df sku
ON i.sku = sku.sku_id AND sku.ds = '${bizdate}'
WHERE o.ds = '${bizdate}';
JOIN 维表必须带 ds。漏了就是全表扫,账单和延迟一起爆。
is_valid_pay 这种旗标,就是口径的钉子。上面所有「GMV」只允许 SUM(IF(is_valid_pay=1, pay_amount, 0)),禁止再写一遍 status IN (...)。
5.3 DWD 回答什么、不回答什么
能回答:这单卖了啥、卖给谁、哪家店、算不算有效支付、毛利多少。
不回答:徐汇店昨天一共多少;比上周好多少;ROI 多少。那些要 GROUP BY 或跨域,属于 DWS / ADS。
有人喜欢把 DWD 做成「大宽表」,用户性别、门店城市、商品类目 20 个属性全冗余进来。轻度冗余我接受(类目、门店 ID),把用户近 30 天标签、门店当月目标也宽进来,DWD 会变成谁都依赖、谁都不敢改的垃圾场。属性放 DIM,事实放 DWD,要用时再 JOIN。
6. DWS:按人会问的粒度先加好
DWS(Data Warehouse Summary,也有人写 Data Warehouse Service,别在命名上较真,看你仓里约定)是轻度汇总层。
「轻度」的意思是:粒度仍然通用,还没有绑死某一张看板。门店 × 天、品类 × 天、渠道 × 天,这些粒度会被很多应用重复问到,所以先加总,避免人人扫 DWD。
6.1 22 号这单,滚进门店天表
dws.dws_trd_shop_nd,ds=20260822,假设当天徐汇店只有这一单有效支付:
| shop_id | channel_id | pay_ord_cnt | pay_usr_cnt | gmv_amt | gross_profit_amt | pay_sku_qty | ds |
|---|---|---|---|---|---|---|---|
| SHOP_08 | POS | 1 | 1 | 199.00 | 99.00 | 3 | 20260822 |
sql
INSERT OVERWRITE TABLE dws.dws_trd_shop_nd PARTITION (ds = '${bizdate}')
SELECT
shop_id,
channel_id,
COUNT(DISTINCT IF(is_valid_pay = 1, order_id, NULL)) AS pay_ord_cnt,
COUNT(DISTINCT IF(is_valid_pay = 1, user_id, NULL)) AS pay_usr_cnt,
SUM(IF(is_valid_pay = 1, pay_amount, 0)) AS gmv_amt,
SUM(IF(is_valid_pay = 1, gross_profit, 0)) AS gross_profit_amt,
SUM(IF(is_valid_pay = 1, sku_qty, 0)) AS pay_sku_qty
FROM dwd.dwd_trd_order_di
WHERE ds = '${bizdate}'
GROUP BY shop_id, channel_id;
23 号退款进另一张 dws.dws_trd_refund_shop_nd:
| shop_id | refund_ord_cnt | refund_amt | ds |
|---|---|---|---|
| SHOP_08 | 1 | 70.00 | 20260823 |
净 GMV 不要回写 22 号的 gmv_amt。 22 号 GMV 永远是 199。净收是应用指标,在 ADS 里用「支付 DWS + 退款 DWS」拼,或单独做一张 dws_trd_shop_net_nd,但分区仍是各自的业务日。老板问「昨天净收」,昨天 = 23 号的话,是 23 号支付减 23 号退款,不是去改 22 号历史。
这一点跟财务月结习惯可能冲突。冲突就单独做财务域,别把经营日分区改成「可变更事实」。两套口径可以并存,必须起不同字段名:gmv_amt vs fin_recog_amt。
6.2 DWS 怎么选粒度
问自己一句:这个 GROUP BY 组合,会不会被 两个以上 的下游用到?
| 粒度 | 表 | 谁会用 |
|---|---|---|
| 天 × 门店 × 渠道 | dws_trd_shop_nd |
门店看板、区域汇总、API |
| 天 × 品类 | dws_trd_cate_nd |
商品运营、滞销分析 |
| 天 × 用户 | 一般不落 DWS | 用户量太大,留 DWD,要复购单独做主题 |
只被一张「双11 活动大屏」用的 40 个字段,别塞进公共 DWS,那是 ADS。
6.3 可加性,这是 DWS 的生命线
gmv_amt、pay_sku_qty 可加:各店加总 = 全网。
pay_usr_cnt 不可跨店加 。同一人当天两店各买一次,店 A 用户 1、店 B 用户 1,全网用户是 1 不是 2。全网用户数必须从 DWD 按天 COUNT DISTINCT user_id,或单独做 dws_trd_usr_nd(无门店维度)。
客单价、转化率、毛利率更不可加。DWS 只存分子分母(gmv_amt、pay_ord_cnt),比率放到 ADS 再除。否则区域汇总把各店客单价平均一下,数是错的。
7. ADS:给报表、API、问数的那一张
ADS(Application Data Service)面向应用。同一份 DWS,可以长出很多张 ADS,互不影响。
7.1 老板看板:一行就够
ads.ads_biz_overview_1d,ds=20260822
| ds | gmv_amt | refund_amt | net_gmv_amt | gross_profit_amt | gross_profit_rate | pay_usr_cnt | pay_ord_cnt | atv_amt |
|---|---|---|---|---|---|---|---|---|
| 20260822 | 199.00 | 0.00 | 199.00 | 99.00 | 0.4975 | 1 | 1 | 199.00 |
ads.ads_biz_overview_1d,ds=20260823(假设 23 号没有新支付,只有退款)
| ds | gmv_amt | refund_amt | net_gmv_amt | ... |
|---|---|---|---|---|
| 20260823 | 0.00 | 70.00 | -70.00 | ... |
客单价 atv_amt = gmv_amt / pay_ord_cnt,毛利率 gross_profit_amt / gmv_amt,同比可以在 ADS 里把 ds-7 的 DWS 取来算好。应用层不要再除一遍。 问数 Agent 更不要自己除------模型口算比率,是我见过最稳的「一本正经胡说」方式。
7.2 门店列表页:另一张 ADS
运营后台要的是列表,不是一行概览:
ads.ads_shop_board_1d
| ds | shop_id | shop_name | city | gmv_amt | gmv_wow_rate | refund_amt | net_gmv_amt |
|---|---|---|---|---|---|---|---|
| 20260822 | SHOP_08 | 徐汇店 | 上海 | 199.00 | 0.12 | 0.00 | 199.00 |
这张表可以冗余 shop_name、city。ADS 允许宽、允许丑、允许只服务这一个应用。DWS 不要为了这张列表去改公共粒度。
7.3 ADS 放哪
查询多、要秒级,进 Hologres 内部表。历史对账、偶尔下钻,MaxCompute 留底即可。
ADS 可以很多张,但每张都要写清楚 消费者。没有消费者的 ADS,三个月后就是没人认领的存储费。
8. 再补一个投放 ROI,看四层怎么接力
订单域还不够说明「为什么不能一层做完」。补一个跨域问题:运营问「这个投放计划花 1 块钱带来多少 GMV」。
源是两套完全不同的系统:
- 交易在 MySQL 订单库
- 投放消耗在媒体后台导出的 CSV(按计划、按天、单位可能是元也可能是分)
错误做法:在 ODS 订单上 JOIN 投放表。订单是行级,投放是计划天级,JOIN 完 GMV 会被放大,ROI 看起来像印钞。
正确接力:
| 层 | 交易侧 | 投放侧 |
|---|---|---|
| ODS | ods_trd_order_di 金额仍是分 |
ods_mkt_ad_cost_di 字段原样,可能叫 spend、cost_fen |
| DWD | 有效支付明细,单位元 | dwd_mkt_ad_cost_di:计划 ID 对齐、单位改元、渠道枚举统一 |
| DWS | dws_trd_shop_nd 或按渠道天汇总 GMV |
dws_mkt_campaign_nd:天 × 计划,cost_amt、展示、点击 |
| ADS | ads_mkt_roi_1d:同一天、同一渠道(或同一计划,若订单能归因)gmv_amt / cost_amt |
归因是脏活:这单算哪条投放,是「最后一次点击」还是「门店自然量」。规则写在 DWD→DWS 的归因表里,ADS 只做除法。规则没定,ADS 不出 ROI,宁可看板空着,也不要一个没人能解释的 4.7。
22 号若投放花费 50 元、GMV 199,ROI = 3.98。23 号退了 70,当日 GMV 0、消耗若还在,ROI 会很难看------这是真实现象,不是分层错误。要看「投放回收」,另做窗口期 ADS(例如消耗日 T 到 T+7 的净 GMV),别去改 T 日那张已经对外的日报。
9. 一张表该进哪层:我用的判断方法
新人每建一张表都来问。我让他们按顺序回答,答到哪一层就放哪一层。
问题 1:有没有改业务含义?
没有,只是从 MySQL / OSS 过来 → ODS。
问题 2:一行还对应一笔业务过程吗(一单、一行 SKU、一次退款、一次消耗)?
是,而且已经统一单位、状态、ID → DWD。
问题 3:是不是 GROUP BY 之后的统计,且粒度能被多个下游复用?
是 → DWS。再问一句:指标可不可加?不可加的比率不要存,存分子分母。
问题 4:是不是只有某一个应用 / 某一类角色在用,字段随需求改得勤?
是 → ADS。
还有两个附加判断:
- 改了这张表,会不会迫使无关的报表一起重跑?会,说明你把 ADS 的东西塞进了 DWS,或把 DWD 的口径复制到了 ADS。
- 这张表能不能用「业务过程 + 粒度」起名而不是用「张总看板 v3」起名?不能,多半是 ADS;能,多半是 DWS。
DIM 的判断更简单:它描述 实体 (商品、门店、用户、日历),不描述 发生过一笔什么。实体放 DIM,事件放 DWD。
10. 常见做错的拆法
ODS 里就算 GMV。 源状态一变,历史 ODS 分区你敢不敢重算?不敢。口径必须离开 ODS。
跳过 DWD,ODS 直接 GROUP BY 出 DWS。 三个 DWS 任务里复制三遍 status IN (3,30),你已经在养下一只口径虫。
DWD 按最后更新时间回写历史分区。 退款把 22 号 GMV 改掉,老板收藏的截图和仓里的数永远对不上。
DWS 存客单价、转化率,再跨店 SUM。 比率不可加,第 6 节写过。
一张 ADS 服务所有人。 三个月后 180 列,其中 120 列只有一个人用过一次。按应用拆表,比按「企业数据资产」堆宽表便宜。
ADS 里再扫 ODS「临时修一下」。 线上出数快,第二次就没人记得这是临时的。禁止跳层任务进生产 DAG。
层名当文件夹,表随便扔。 ods schema 下出现 ads_gmv_final_new。名字即契约,破了就没有分层。
为分层而分层。 全公司就 8 张报表、两张源表,硬套四层,调度 40 个节点。那种规模,ODS + DWD + 一张 ADS 就够,DWS 等出现第二个复用粒度再加。层是手段,不是 KPI。
11. 和调度、质量怎么配合
分层不是只画画架构图,任务依赖要跟层走。
同步 ODS 订单 ─┐
同步 ODS 明细 ─┼─ DIM 门店 / 商品 ─ DWD 销售事实 ─ DWS 门店天 ─ ADS 概览 / 门店看板
同步 ODS 退款 ─┘ └─ DWD 退款事实 ─ DWS 退款店天 ─┘
质量规则也按层设,别只在 ADS 卡一行非空:
| 层 | 最少规则 | 失败了说明什么 |
|---|---|---|
| ODS | 当天行数 > 0;源主键唯一 | 同步断了或抽重了 |
| DWD | pay_amount >= 0;头行金额差为 0 或进异常表;is_valid_pay 只有 0/1 |
口径 SQL 或源脏数据 |
| DWS | SUM(gmv) 与 DWD 加总误差 < 0.01 |
GROUP BY 漏维度或滤错旗标 |
| ADS | 当天恰好一行概览;对外字段和 DWS 对得上 | 导入失败或比率算错 |
强规则挂在节点上,失败阻断下游。ADS 没产出,接口要返回「数据未就绪」,不要把昨天的数顶上去冒充今天。
重跑边界:
- 源抽错 → 重跑 ODS 当天 → 自动触发下游(overwrite 分区)
- 支付状态枚举漏了 → 只改 DWD 再往下,不用重抽 ODS
- 看板多一个同比 → 只改 ADS
这才是分层从「概念」变成「能修生产」的地方。
四层可以背成四句话,但真正能干活的是这一单走下来的约束:
ODS 保住和源库对得上;DWD 保住「什么叫卖出去」只写一次;DWS 保住按店按天能加、能复用;ADS 保住老板、接口、问数看到的是同一套已经算好的数。
你把 22 号那笔 199 元支付、23 号那笔 70 元退款,分别放在它们该在的分区里,经营分析才站得住。层可以少,口径不能散;表可以宽,流向不能反。
和上一篇搭建文对着看:上一篇是这三个产品怎么接,这篇是接上之后表为什么不能摊在一层。两件事一起做完,仓才算开始干活,而不是只是「云上有几张表」。