04四级净水:SDI → DWI → DWR → DWS → ADS目录
同一份数据在湖里存五遍,不是浪费磁盘:明细留着查问题,汇总拿去出报表,口径只写一遍——所以越往下水越少,也越贵。
同一池水,为什么要舀进五个池子?因为"去泥沙""去杂质""调硬度"是三件不同的事,一个池子干不完。而且每过一池,水少一截、干净一截,到最后灌瓶那一格,一滴都不许再改配方。

一、先说人话

上一关水已经进了 SDI。这一关的问题,是每个看报表的人都会问的那句:同一份销售出库明细,SDI 一份、DWI 一份、DWR 一份、DWS 又一份、ADS 再来一张——磁盘上明明是同一件事,为什么存五遍?

三个理由,缺一个这套分层就塌一半。

为什么存好几遍说人话
① 粒度不同明细查问题:一行一笔业务,出事能点到哪一笔。汇总出报表:一个主题一个月一个组织一行,报表才跑得动。
② 算一次,十处用别让每个页面在后台自己再算一遍。同一份汇总,十个页面都从这儿拿。
③ 口径只落一处同一个口径写在五个页面,就是五个数。写在一层,改一次全对。

三条在原文里各有名字:共享复用原则(能复用则复用,能汇聚共享则汇聚共享,不做烟囱式开发)、历史数据堆积原则(能积累则积累,不重复计算)、职责分工原则(多源整合、标准化只在 dwi 层做;清洗转换只在 dwr 层做)。

这三条不是设计美学,是省工时。原文那句话说得比我还直:不做烟囱式开发。烟囱式就是每个页面自己竖一根管子,十根管子十套算法,对账的时候十个数。

还有一条兜底的:分层信任原则——dws 层和 ads 层应当信任各自上层的数据没有异常,不需要做额外的异常处理。这条既解释了上层为什么敢直接用下层,也解释了为什么口径必须一次写对:下面写错,上面没有第二次机会。

小窍门反过来验一件事:如果你发现某个页面在后台又算了一遍,别夸它勤快——那是分层没落到位。它今天跟隔壁页面算得一样,不代表明天还一样。

二、拆开看:五口池子,每口只干一件事

先看形状。水从上往下走,池子一个比一个窄;窄的不是偷工减料,是水真的少了——每过一层,被规则砍掉一批,被汇总收掉一批。

同一份数据,五口池子 池子宽度 = 还剩多少行 SDI 贴源层 沉淀池 · 原样落地,只加技术字段 DWI 明细层 粗滤池 · 多源合并、统一字段名,一行一笔业务 DWR 汇总明细 精滤池 · 按业务键收敛,业务规则在这层落 DWS 汇总层 配料罐 · 主题 × 月 × 组织 DM / ADS 应用层 灌装线 · 一个场景一张表 越往下 水量越少 每滴越贵

五口池子顺序不能换。下面一口一口看,每口按"像什么 / 其实是什么 / 真名 / 错了怎样"说一遍——这是这一关唯一需要硬记的东西。

2.1 SDI 贴源层 · 沉淀池

🪣 SDI 贴源层(沉淀池)

像什么:厂里最靠河的那口池子。水刚抽进来,连泥带沙都在里头;这一池什么也不加,只让大颗粒先沉下去。

其实是什么:和源系统的表长得几乎一样,原样落地,不加工;只多几列框架字段(分区、入湖时间这类技术字段)。原文把界限划得很清楚:sdi 是近源层,入湖的数据存在这一层,而 ETL 的工作范围是 dwi、dwr、dim、dws、ads——ETL 的活从 DWI 才开始。

真名
gk${公司缩写}_${系统名}_${系统类型}
真例
gkjx_cms_dis6.scm_salbill_hdr(江西销售,CMS 系统的销售出库单头表)
干什么用
倒查源头:某个数不对,能一层层回到源表那一行
会出事在 SDI 里"顺手"改个值、删几行脏数据。舒服三天,然后永远对不上账:贴源层入湖数据检查是拿 SDI 跟源端比的,SDI 一动,基准就没了。

2.2 DWI 明细层 · 粗滤池

🧽 DWI 明细层(粗滤池)

像什么:第二口池子,开始加药、撇沫、粗滤。上游几根取水管抽来的水到这儿合成一股,泥少了,但水还是一池水。

其实是什么:多源合并、统一字段名、关联主数据、套汇率和税率,一行还是一笔业务。原文规定:多源整合、标准化工作原则上只在 dwi 层完成。复盘给它八个字——业务明细,行级、可追溯。

真名
dwi_gk_hudi.dwi_*
真例
dwi_gk_hudi.dwi_sad_se_sale_outb_ord_line(销售出库单行)
这层最典型的一件活
改名:源系统那张表里叫 stopflag,到湖里统一叫 delete_status
会出事字段名不统一就往下走。几个源系统对"这行删没删"各有各的叫法,下游每层都得写一段特判;哪天某个源系统又改了名,改的是你下游的 SQL,不是它的表。

2.3 DWR 汇总明细 · 精滤池

🧪 DWR 汇总明细(精滤池)

像什么:第三口,精滤。这一池不再追究"哪一滴水从哪根管子来",只按标准把该留的留下、该放掉的放掉。

其实是什么:按业务键收敛,业务规则在这一层落。原文规定:清洗转换(包含异常处理)工作原则上只在 dwr 层完成。收敛的意思是"同一笔业务的多个动作合成一条",不再是流水账,但还是明细。

真名
dwr_gk_hudi.dwr_*
真实规则
A1.rele_date is not null(这条写在 DWR 层,不是写在页面上)
为什么规则在这
清洗转换只在这层做,别处一律不做,所以口径只有这一份
注意这条规则长得极不起眼:一个 is not null。但它决定了"还没释放"的单算不算数。它要是被挪到 ADS 去写,十个页面就有十个版本的"释放"。

2.4 DWS 汇总层 · 配料罐

🥣 DWS 汇总层(配料罐)

像什么:第四口,成品水罐。按配方把硬度、余氯调到出厂标准;到这一罐,水里已经看不出"哪一滴是哪个取水口的"了——那些细节是被有意丢掉的。

其实是什么:按主题 × 月份 × 组织汇总(复盘原话:主题汇总,宽表 / 月度 / 日汇总)。粒度一粗,行数自然塌下去,塌多少看场景,这一关不给数。它是下游 2.0 和报表的输入,不是结果。

真名
表名的分层前缀就是 dws(原文:分层前缀只含 dwi / dwr / dim / dws / ads);落哪个库按表的实际层级走——湖里 dwi_gk_hudi / dwr_gk_hudi / dm_gk_hudi,仓里 dm_gk_dws
在血缘里的位置
帆软那张表级监控模型里有一列就叫 dws_table:页面 → 控件 → 指标 → DWS 表 → 调度任务,一条线穿下来
会出事粒度拍错。该按月的按了天,该留组织的把组织维度丢了:往上一路对不回来,往下一年跑下来只为了出一个数。

2.5 DM / ADS 应用层 · 灌装线

🍾 DM / ADS 应用层(灌装线)

像什么:灌装线。到这儿水不再论吨,按瓶灌、贴标签、装箱——一个瓶口对页面上的一个位置。

其实是什么:一个场景一张表,字段就是页面要的字段。原文的定位是:ads 层接口表模型应该面向公司数据分析业务建模(dwi、dwr、dim、dws 这几层面向的是公司运营业务建模)。所以这层不追求通用,只追求"这个页面能直接用"。

真名
dm_gk_hudi.ads_*
真例
dm_gk_hudi.ads_sad_se_jx_dtl、dm_gk_hudi.ads_pur_ps_channel
再往后
SeaTunnel 同步进 Doris 的 dm_gk_dws.ads_*,页面直连取数
会出事把 ADS 当明细表用。页面临时要一张明细清单,看着这张表里字段差不多就上手了——可它是灌装好的瓶子,滤掉的颗粒回不来,明细只能回 DWR 或 DWI 取。

2.6 命名规范:门牌号怎么钉

🏷 五条命名规矩(一条都别自己发挥)

像什么:一条街上五排房子,门牌有规矩:第几排、第几号、是正房还是偏房。看一眼门牌就知道该不该进这个门。

其实是什么:名字本身就是元数据。原文的命名规范一共五条,逐条给:

① 贴源层
gk${公司缩写}_${系统名}_${系统类型},例 gksd_cms_dis6
② 其他层
${数据分层}_${场景}_${一级}_${二级}_…,级别按词根词条走;词根表是探源库里的 data_dqc.t_gov_asset_name
③ 中间过程表
和目标表同层建,表名最后加 _mid;多张中间表就在后缀上补信息,_mid_sale、_mid_sale_2
④ 临时表
tmp_${负责人缩写}_${目标表名}_${日期},例 tmp_xlq_dim_system_code_1120
⑤ 库名
按层级分:湖 dwi_gk_hudi / dwr_gk_hudi / dm_gk_hudi;仓 dm_gk_dws
真名示例
dm_gk_hudi.ads_pur_ps_channel——库名 + 分层前缀 + 后面的词根,一眼看出是 ADS 层的采购渠道表
注意同一套公式,湖和仓并不完全一样。湖里那条是 ${数据分层}_${场景}_${一级}_${二级}_…;Doris 那章的写法是 ${数据分层}_${一级}_${二级}_${三级}_${其他},没有"场景"那一段,而且明确分层前缀只含 dwi / dwr / dim / dws / ads。照抄公式之前先看清你在湖里还是在仓里。
会出事中间表建到隔壁层去,比如给 DWR 建一张 dwi_* 的中间表。血缘一画出来像是"DWR 从 DWI 取数",其实取的是自家的临时产物;名字合法、位置不对,谁都不会报错。原文这条很硬:中间过程表和目标表在同一层建立。

2.7 表类型怎么选:同样是池子,砌法不一样

🧱 表类型选型(Hive / Hudi / 各种专用表)

像什么:池子有水泥的、有钢板的、有一次性的。装什么水、换不换水,决定这口池子怎么砌。

其实是什么:原文按"数据长什么样"分派表类型。选错了不是慢一点,是根本存不对。

这种数据用哪种表 · 必须带的属性
源端每日全量表Hive 表:type = 'parquet'
要增量、要更新Hudi 表:type = 'cow',加主键 primaryKey = 'system_code,biz_id',加预聚合键 preCombineField = 'hoodie_event_time'
快照表 / 中间表 / 历史表 / 拉链表四种各有既定写法;原文这四节只给了标题、正文没展开,实际写法要现场核
临时表物理临时表,建成 Hive 表(type = 'parquet');命名参考 Hudi 表的命名规范

两条硬约束先钉住:Hudi 的主键必须存在,可以一个字段也可以多个;preCombineField 的类型只能是 Date 或 Timestamp。

真名
type='cow' · primaryKey='system_code,biz_id' · preCombineField='hoodie_event_time' · type='parquet'
出处
《数据实施规范》1.2 表类型选型规范、1.4 Hive / Hudi 表属性
注意CTAS / WITH AS 这件事,两段原文要合起来读。1.2.7 说:能按 create table as、with as 写出来的内容,建物理临时表,建成 Hive 表;4.4.1 说:禁止用 with ... as ...、create table as ... 的方式去建临时表或临时数据集。合起来只有一句——临时结果要落成一张真表,不在脚本里内联写一段 CTE 糊过去。
会出事Hudi 表漏了主键,或者把 preCombineField 的类型写成 String。前者建表这一关就过不去;后者预聚合失效,同一笔业务的几次变更不会被合并成最后一行,下游拿到几行"看着都对"的数据。

三、串起来:一条真实血缘走完全程

前面五口池子都是静态的。真实的活是这么走的:工商协同 / 江西销售的销售出库,从 CMS 的源表一路走到页面那张结果表。复盘里这条链是完整写着的,一个环节都没省。

  1. SDI 原样落地。gkjx_cms_dis6.scm_salbill_hdr.stopflag——江西销售、CMS 的销售出库单头表,stopflag 是源系统自己给"这单算不算废了"起的名字。这一层一行还是一行,一个字没动。
  2. DWI 翻译 + 合并。落到 dwi_gk_hudi.dwi_sad_se_sale_outb_ord_line.delete_status。多源整合、统一字段名只在这层做,所以源系统的 stopflag 到湖里统一叫 delete_status。这一步砍掉的不是数据,是名字——同一个含义在几个系统里叫几个名,到这儿只剩一个名。
  3. DWR 砍掉没释放的。规则就是 A1.rele_date is not null。释放日期为空,说明这单还没放行、还没真正生效;留着它,在途、待处理全混进统计。这一步砍掉的是未释放。
  4. ADS 再砍两刀。落到 dm_gk_hudi.ads_sad_se_jx_dtl,条件是 sale_outb_ord_status='60' 且 delete_status='00'。第一刀砍掉没走到 60 那一步的(在途、退回、作废取消那批),第二刀砍掉 delete_status 不是 '00' 的,也就是已删除的。
  5. Doris 到页面。SeaTunnel 把这张 ADS 表同步进仓里的 dm_gk_dws.ads_*,页面直连 Doris 取数。到这一步没有新的砍法,只有搬运。

把这五步砍掉的东西摆在一起看,规律很清楚:

这一步砍掉了什么为什么砍在这
SDI → DWI名字不一致,不是数据整合与标准化只在 DWI 做
DWI → DWR未释放的清洗转换只在 DWR 做
DWR → ADS没到 60 的(含取消)、已删除的ADS 只留页面要的那批
ADS → 页面不砍,只搬口径已定死,再动就是改别人的数

顺序为什么不能换?因为每层只判自己该判的那一条。反过来做——把 rele_date is not null 挪到 ADS 去写——你今天写的是这一张表的 WHERE,明天隔壁页面从 DWI 直接取数,就漏了这条规则,于是同一个出库量两个数,而且两个都说得通。

注意这两个状态码的确切含义,原文只给了字面条件、没给码表:sale_outb_ord_status='60' 里的 60 到底是"已出库"还是"已开票",delete_status='00' 是不是"正常未删除"——按业务字典核,别照我这句猜。

四、搞错了会怎样

两个误解,都撞在同一堵墙上——原文的分层信任原则:各层不把异常数据传到下一层;除了整合层(dwi)和清洗转换层(dwr、dim),dws 层和 ads 层应当信任各自上层的数据没有异常,不需要做额外的异常处理。

注意 · 以为 DWS 会去重不会。去重、状态过滤、汇率税率这些活,复盘那张分层图就写在 DWI 到 DWR 那根箭头上;DWS 的职责是汇总,不是再洗干净一遍。所以 DWS 里出现重复行,先别怀疑 DWS 的 SQL——重复是从底层一路带上来的,你在这儿加个 distinct,只是从池子里捞沙子,沙子还在下面。
会出事 · 到 ADS 还想改口径越往后改,影响面越大、越贵。ADS 下面直接挂着全部页面和报表:帆软那张表级监控模型把"页面 → 控件 → 指标 → dws_table → 调度任务"串成一条线,你在 ADS 改一个 WHERE,改的是这条线上所有人的数;同一份口径如果当初落在 DWR,改一次就全都对。更麻烦的是,这一层本来就该信任上游,没人为你的改动兜底。

两个误解的根子是一个:把层当成多存的一份拷贝,而不是多一道责任。层是有分工的,谁该干的活谁干;跨层去补,看着快,账迟早要还。

小窍门真觉得 ADS 上非改不可,先回答一个问题:这条口径当初为什么没落在 DWR?答不上来就别动。动了以后下一次对账,两个数都"有据可查",谁都说不清哪个是对的。

五、一句话记住

做对了层不是给机器看的,是给人查问题用的——出事时能一层一层往上定位。

具体怎么用:告警说"江西销售出库数不对"。先看 ADS 那张 ads_sad_se_jx_dtl 的两个条件还在不在;再往上一格,看 rele_date is not null 有没有被谁顺手改掉;还不对,回到 dwi_gk_hudi.dwi_sad_se_sale_outb_ord_line 看 delete_status 有没有把 stopflag 翻错。三层看完,问题卡在哪一层就出来了——这就是分层唯一的用处。

反过来说,如果你只能对着 ADS 那一张表发呆,说明前面几层白存了。

事实来源:数据实施规范 1.1–1.4 / 2.1.1 / 4.4.1 / 5.1 · 复盘"一条真实血缘"这一关只管"分层"下一关:Spark SQL 怎么灌瓶