一、先说人话
上一关水已经进了 SDI。这一关的问题,是每个看报表的人都会问的那句:同一份销售出库明细,SDI 一份、DWI 一份、DWR 一份、DWS 又一份、ADS 再来一张——磁盘上明明是同一件事,为什么存五遍?
三个理由,缺一个这套分层就塌一半。
| 为什么存好几遍 | 说人话 |
|---|---|
| ① 粒度不同 | 明细查问题:一行一笔业务,出事能点到哪一笔。汇总出报表:一个主题一个月一个组织一行,报表才跑得动。 |
| ② 算一次,十处用 | 别让每个页面在后台自己再算一遍。同一份汇总,十个页面都从这儿拿。 |
| ③ 口径只落一处 | 同一个口径写在五个页面,就是五个数。写在一层,改一次全对。 |
三条在原文里各有名字:共享复用原则(能复用则复用,能汇聚共享则汇聚共享,不做烟囱式开发)、历史数据堆积原则(能积累则积累,不重复计算)、职责分工原则(多源整合、标准化只在 dwi 层做;清洗转换只在 dwr 层做)。
这三条不是设计美学,是省工时。原文那句话说得比我还直:不做烟囱式开发。烟囱式就是每个页面自己竖一根管子,十根管子十套算法,对账的时候十个数。
还有一条兜底的:分层信任原则——dws 层和 ads 层应当信任各自上层的数据没有异常,不需要做额外的异常处理。这条既解释了上层为什么敢直接用下层,也解释了为什么口径必须一次写对:下面写错,上面没有第二次机会。
二、拆开看:五口池子,每口只干一件事
先看形状。水从上往下走,池子一个比一个窄;窄的不是偷工减料,是水真的少了——每过一层,被规则砍掉一批,被汇总收掉一批。
五口池子顺序不能换。下面一口一口看,每口按"像什么 / 其实是什么 / 真名 / 错了怎样"说一遍——这是这一关唯一需要硬记的东西。
2.1 SDI 贴源层 · 沉淀池
像什么:厂里最靠河的那口池子。水刚抽进来,连泥带沙都在里头;这一池什么也不加,只让大颗粒先沉下去。
其实是什么:和源系统的表长得几乎一样,原样落地,不加工;只多几列框架字段(分区、入湖时间这类技术字段)。原文把界限划得很清楚:sdi 是近源层,入湖的数据存在这一层,而 ETL 的工作范围是 dwi、dwr、dim、dws、ads——ETL 的活从 DWI 才开始。
- 真名
gk${公司缩写}_${系统名}_${系统类型}- 真例
gkjx_cms_dis6.scm_salbill_hdr(江西销售,CMS 系统的销售出库单头表)- 干什么用
- 倒查源头:某个数不对,能一层层回到源表那一行
2.2 DWI 明细层 · 粗滤池
像什么:第二口池子,开始加药、撇沫、粗滤。上游几根取水管抽来的水到这儿合成一股,泥少了,但水还是一池水。
其实是什么:多源合并、统一字段名、关联主数据、套汇率和税率,一行还是一笔业务。原文规定:多源整合、标准化工作原则上只在 dwi 层完成。复盘给它八个字——业务明细,行级、可追溯。
- 真名
dwi_gk_hudi.dwi_*- 真例
dwi_gk_hudi.dwi_sad_se_sale_outb_ord_line(销售出库单行)- 这层最典型的一件活
- 改名:源系统那张表里叫
stopflag,到湖里统一叫delete_status
2.3 DWR 汇总明细 · 精滤池
像什么:第三口,精滤。这一池不再追究"哪一滴水从哪根管子来",只按标准把该留的留下、该放掉的放掉。
其实是什么:按业务键收敛,业务规则在这一层落。原文规定:清洗转换(包含异常处理)工作原则上只在 dwr 层完成。收敛的意思是"同一笔业务的多个动作合成一条",不再是流水账,但还是明细。
- 真名
dwr_gk_hudi.dwr_*- 真实规则
A1.rele_date is not null(这条写在 DWR 层,不是写在页面上)- 为什么规则在这
- 清洗转换只在这层做,别处一律不做,所以口径只有这一份
is not null。但它决定了"还没释放"的单算不算数。它要是被挪到 ADS 去写,十个页面就有十个版本的"释放"。2.4 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 应用层 · 灌装线
像什么:灌装线。到这儿水不再论吨,按瓶灌、贴标签、装箱——一个瓶口对页面上的一个位置。
其实是什么:一个场景一张表,字段就是页面要的字段。原文的定位是: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_*,页面直连取数
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。照抄公式之前先看清你在湖里还是在仓里。dwi_* 的中间表。血缘一画出来像是"DWR 从 DWI 取数",其实取的是自家的临时产物;名字合法、位置不对,谁都不会报错。原文这条很硬:中间过程表和目标表在同一层建立。2.7 表类型怎么选:同样是池子,砌法不一样
像什么:池子有水泥的、有钢板的、有一次性的。装什么水、换不换水,决定这口池子怎么砌。
其实是什么:原文按"数据长什么样"分派表类型。选错了不是慢一点,是根本存不对。
| 这种数据 | 用哪种表 · 必须带的属性 |
|---|---|
| 源端每日全量表 | 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 表属性
create table as、with as 写出来的内容,建物理临时表,建成 Hive 表;4.4.1 说:禁止用 with ... as ...、create table as ... 的方式去建临时表或临时数据集。合起来只有一句——临时结果要落成一张真表,不在脚本里内联写一段 CTE 糊过去。preCombineField 的类型写成 String。前者建表这一关就过不去;后者预聚合失效,同一笔业务的几次变更不会被合并成最后一行,下游拿到几行"看着都对"的数据。三、串起来:一条真实血缘走完全程
前面五口池子都是静态的。真实的活是这么走的:工商协同 / 江西销售的销售出库,从 CMS 的源表一路走到页面那张结果表。复盘里这条链是完整写着的,一个环节都没省。
- SDI 原样落地。
gkjx_cms_dis6.scm_salbill_hdr.stopflag——江西销售、CMS 的销售出库单头表,stopflag是源系统自己给"这单算不算废了"起的名字。这一层一行还是一行,一个字没动。 - DWI 翻译 + 合并。落到
dwi_gk_hudi.dwi_sad_se_sale_outb_ord_line.delete_status。多源整合、统一字段名只在这层做,所以源系统的stopflag到湖里统一叫delete_status。这一步砍掉的不是数据,是名字——同一个含义在几个系统里叫几个名,到这儿只剩一个名。 - DWR 砍掉没释放的。规则就是
A1.rele_date is not null。释放日期为空,说明这单还没放行、还没真正生效;留着它,在途、待处理全混进统计。这一步砍掉的是未释放。 - ADS 再砍两刀。落到
dm_gk_hudi.ads_sad_se_jx_dtl,条件是sale_outb_ord_status='60'且delete_status='00'。第一刀砍掉没走到 60 那一步的(在途、退回、作废取消那批),第二刀砍掉delete_status不是'00'的,也就是已删除的。 - 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 层应当信任各自上层的数据没有异常,不需要做额外的异常处理。
distinct,只是从池子里捞沙子,沙子还在下面。dws_table → 调度任务"串成一条线,你在 ADS 改一个 WHERE,改的是这条线上所有人的数;同一份口径如果当初落在 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 那一张表发呆,说明前面几层白存了。