From 1f6f66fdf50069a99e663c58f3f7e1640fd5f992 Mon Sep 17 00:00:00 2001 From: jnMetaCode Date: Tue, 9 Jun 2026 15:30:56 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20data-warehouse?= =?UTF-8?q?-modeling=20=E6=95=B0=E4=BB=93=E5=BB=BA=E6=A8=A1=20skill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 数仓建模实战方法论,覆盖: - 5 种建模方法论对比选择(Kimball/Inmon/Data Vault 2.0/OneData/Medallion) - 4 种分层架构(国内标准/Medallion/dbt/Data Vault) - 维度建模、SCD 处理、指标体系、总线矩阵 - 命名规范与词根词典 - 各层 SQL 模板(Hive/Spark) - 实时数仓(Kafka+Flink+OLAP) - 反模式识别(P0/P1/P2 分级) - dbt 工程化完整指南 - 云平台最佳实践(Snowflake/BigQuery/Databricks/Redshift) - 数据治理与合规(GDPR/CCPA/PII) - 数仓文档模板与评审清单 实战语气,非教科书摘要。 --- skills/data-warehouse-modeling/SKILL.md | 149 ++++++++ .../references/antipatterns.md | 107 ++++++ .../references/bus-matrix.md | 92 +++++ .../references/cloud-platform-practices.md | 183 ++++++++++ .../references/data-governance.md | 195 ++++++++++ .../references/dbt-practices.md | 192 ++++++++++ .../references/dw-doc-standards.md | 142 ++++++++ .../references/layer-architecture.md | 120 ++++++ .../references/methodology-comparison.md | 144 ++++++++ .../references/naming-conventions.md | 141 +++++++ .../references/realtime-dw-design.md | 120 ++++++ .../references/subject-domains.md | 35 ++ .../scripts/sql-templates.md | 343 ++++++++++++++++++ 13 files changed, 1963 insertions(+) create mode 100644 skills/data-warehouse-modeling/SKILL.md create mode 100644 skills/data-warehouse-modeling/references/antipatterns.md create mode 100644 skills/data-warehouse-modeling/references/bus-matrix.md create mode 100644 skills/data-warehouse-modeling/references/cloud-platform-practices.md create mode 100644 skills/data-warehouse-modeling/references/data-governance.md create mode 100644 skills/data-warehouse-modeling/references/dbt-practices.md create mode 100644 skills/data-warehouse-modeling/references/dw-doc-standards.md create mode 100644 skills/data-warehouse-modeling/references/layer-architecture.md create mode 100644 skills/data-warehouse-modeling/references/methodology-comparison.md create mode 100644 skills/data-warehouse-modeling/references/naming-conventions.md create mode 100644 skills/data-warehouse-modeling/references/realtime-dw-design.md create mode 100644 skills/data-warehouse-modeling/references/subject-domains.md create mode 100644 skills/data-warehouse-modeling/scripts/sql-templates.md diff --git a/skills/data-warehouse-modeling/SKILL.md b/skills/data-warehouse-modeling/SKILL.md new file mode 100644 index 0000000..a75dc25 --- /dev/null +++ b/skills/data-warehouse-modeling/SKILL.md @@ -0,0 +1,149 @@ +--- +name: data-warehouse-modeling +description: > + 数仓建模实战方法论。Use when: (1) 设计分层架构 (ODS/DWD/DWS/ADS 或 Bronze/Silver/Gold) + (2) 维度建模——事实表、维度表、星型模型 (3) 指标体系 (4) 主题域划分、总线矩阵 + (5) 命名规范、词根词典 (6) 建模评审 (7) 选方法论——Kimball/Inmon/Data Vault/OneData/Medallion + (8) 湖仓一体 (9) 实时数仓 (10) SCD 处理 (11) 反模式排查 (12) dbt 工程化 + (13) 云平台数仓——Snowflake/BigQuery/Databricks/Redshift (14) 数据治理——GDPR/CCPA。 + 用户说"帮我建张表""这个指标怎么定义""数仓怎么分"时触发。 +--- + +# 数仓建模 + +基于 Kimball、Data Vault 2.0、阿里 OneData、Databricks Medallion 四套主流方法论的实战建模指南。 +不是教科书摘要——是踩过坑之后的判断框架。 + +## 核心约束 + +这几条不是"建议",是经验教训换来的硬边界。但在说"绝不"之前,先想清楚你面对的约束。 + +**1. 数据单向流动,但允许受控的快捷路径** +- 标准路径:ODS → DWD → DWS → ADS,逐层加工 +- 现实中 ADS 偶尔需要读 DWD(如敏捷看板要明细数据),可以,但必须在 ADS 层注释标注原因,且不得成为常态 +- 绝对禁止:ADS 直接读 ODS + +**2. 公共计算只做一次——但"公共"的判定标准是复用次数** +- 3 个以上下游消费的计算,必须下沉到 DWS +- 只有 1-2 个下游时,允许在 ADS 各自计算,但文档中标注口径一致性责任人 +- 新建公共指标的前提:已存在至少 2 个重复实现 + +**3. 每张事实表必须声明粒度** +- 粒度声明不是表注释里写一行就完了——是 DDL 上方的 block comment,写清楚"一行代表什么" +- 混合粒度是事实表设计中最常见的致命错误,没有之一 +- 拿不准粒度时,选最细粒度。聚合总是安全的,拆分总是痛苦的 + +**4. 原子指标口径唯一** +- 同一个"支付金额",全数仓只能有一个 SQL 定义 +- 口径分歧不是技术问题,是组织问题。解决不了组织问题,数仓分层再漂亮也是白搭 +- 先把原子指标的定义权收归数据团队,再做分层 + +**5. 维度必须统一,但不必一步到位** +- 先统一核心维度(用户、商品、时间、地域),业务维度允许各域自行管理 +- 一致性维度的建设进度,直接决定跨域分析能走多远 +- dim_user 表如果两个团队各维护一份,跨域分析就不要想了 + +**6. 命名规范比你想的更重要** +- 不是为了好看,是为了让新入职的人 3 天内能看懂表结构 +- 词根词典的核心价值:减少命名歧义带来的沟通成本 +- 一旦规范确定,严格执行。宁可改 10 个表名,不留 1 个特例 + +## 方法论选择——从现实约束出发 + +别选"最好的"方法论,选你的团队能落地的。 + +| 你面对的现实 | 踏实的做法 | 为什么 | +|------------|-----------|-------| +| 团队 5 人以下,业务跑得快 | Kimball 星型模型,先出东西再说 | 6 种方法论里最快见效的 | +| 公司要求数据口径统一,但各业务线各搞各的 | OneData 指标体系 + Kimball 建模 | 不上指标体系,口径问题无解 | +| 金融/医疗,数据变更必须可追溯 | Data Vault 2.0 做整合层,Kimball 做报表层 | Vault 的 insert-only 天然满足审计,但报表层还是 Kimball 好用 | +| 已有数据湖,想加 BI 能力 | Medallion (Bronze/Silver/Gold) | 不用搬数据,在湖上建层 | +| 有 dbt 工程师,追求工程化 | dbt + 星型模型 | 版本控制、测试、文档一条龙 | +| 历史包袱重,几十个老旧 ODS 表 | 先做 DWD 清洗层,别动 ODS | 稳住底层再往上走 | + +→ `references/methodology-comparison.md` — 方法论详细对比和混合架构 + +## 分层架构 + +四套主流分层模式,本质做的事一样,叫法不同: + +| 模式 | 分层 | 来源 | 适合谁 | +|------|------|------|-------| +| 国内标准 | ODS → DWD → DWS → ADS (+DIM) | 阿里 OneData | 国内互联网公司 | +| Medallion | Bronze → Silver → Gold | Databricks | 湖仓一体、Delta Lake | +| dbt | sources → staging → intermediate → marts | dbt Labs | 现代数据栈团队 | +| Data Vault | Raw Vault → Business Vault → Info Marts | Dan Linstedt | 强审计行业 | + +跨模式映射:Bronze ≈ ODS, Silver ≈ DWD, Gold ≈ DWS+ADS, staging ≈ DWD, marts ≈ DWS+ADS + +→ `references/layer-architecture.md` — 各层职责、5 家企业对比、分层原则 + +## 维度建模要点 + +Kimball 四步法的核心不是四个步骤本身,而是步骤的顺序。 + +**第一步永远是选业务过程,不是选维度。** 先确定"我要分析什么事件"(下单、支付、退款),再问"从哪些角度分析"。搞反了就是灾难。 + +**粒度声明必须具体到让 SQL 能写出来。** "订单粒度"不够——要写清楚是"每笔订单"还是"每笔订单中的每个商品行"。粒度决定了事实表能不能正确聚合。 + +**事实表设计的选择:** +- 事务事实表:记录事件发生。适合大多数场景 +- 周期快照表:每天/每小时拍一张状态快照。适合余额、库存这类状态型数据 +- 累积快照表:一条记录贯穿一个业务全流程。适合有明确里程碑的流程(下单→支付→发货→签收) + +**维度退化的取舍:** +- DWD 层高频维度退化到事实表,减少 JOIN,这是正确做法 +- 但退化的是"查询时一定需要"的属性,不是"万一有用"的属性 +- 退化维度变了怎么办?DWD 层接受用当时的值。历史追溯靠 DIM 层 + +**SCD 选择:** +- 需要历史追溯 → SCD2(拉链表),没有例外 +- 不需要历史 → SCD1 直接覆盖 +- SCD3(只保留前后两个值)在实际项目中几乎不用,别被教科书忽悠 + +## 指标体系 + +阿里 OneData 的核心贡献不是技术框架,是**指标定义的标准化流程**。 + +``` +原子指标 = 业务过程 + 度量 + 例:支付成功的订单金额 + +派生指标 = 原子指标 + 业务限定 + 时间窗口 + 统计粒度 + 例:近 7 天母婴品类按省份的支付金额 + +复合指标 = 派生指标之间的运算 + 例:客单价 = 支付金额 / 支付用户数 +``` + +**关键认知:** 原子指标的口径对齐是数据治理中最难的部分,没有捷径。业务方说"GMV"时,你至少要追问三个问题:含不含退款?含不含优惠?统计时间截止点是什么?把这三个问题的答案写进指标定义文档,比建任何模型都重要。 + +## 参考文件索引 + +| 文件 | 内容 | +|------|------| +| `references/methodology-comparison.md` | 四种方法论详细对比、决策树、混合架构 | +| `references/layer-architecture.md` | 各层职责、5 家企业对比 | +| `references/subject-domains.md` | 12 个主题域、业务过程 | +| `references/bus-matrix.md` | 总线矩阵设计、维度 DDL | +| `references/naming-conventions.md` | 表/字段命名、词根词典、数据类型 | +| `scripts/sql-templates.md` | 各层 DDL/DML 模板(Hive/Spark) | +| `references/realtime-dw-design.md` | Kafka+Flink+OLAP 实时数仓 | +| `references/antipatterns.md` | 反模式排查(P0/P1/P2 分级) | +| `references/dbt-practices.md` | dbt 工程化完整指南 | +| `references/cloud-platform-practices.md` | Snowflake/BigQuery/Databricks/Redshift | +| `references/data-governance.md` | GDPR、CCPA、数据分级、PII 脱敏 | +| `references/dw-doc-standards.md` | 设计文档模板、评审清单 | + +## 建模工作流 + +``` +1. 业务调研 → 别急着画架构图,先搞清楚业务方到底要什么 +2. 架构设计 → 选方法论、划分主题域、画总线矩阵 +3. 规范定义 → 词根词典、命名规范、指标口径(这步不能省) +4. 模型设计 → ODS 镜像 → DWD 粒度+退化 → DWS 汇总 → ADS 服务 +5. 评审 → 粒度、跨层、主键、口径、命名 +6. 上线运维 → 质量监控、血缘、SLA +``` + +**调研阶段的实际操作:** 拿到需求后,不要先想"怎么建模"。先跟业务方确认三件事:决策场景是什么、要看的指标有哪些、数据从哪来。这三件事确认清楚了,分层和模型设计自然浮出水面。 diff --git a/skills/data-warehouse-modeling/references/antipatterns.md b/skills/data-warehouse-modeling/references/antipatterns.md new file mode 100644 index 0000000..c2eae9d --- /dev/null +++ b/skills/data-warehouse-modeling/references/antipatterns.md @@ -0,0 +1,107 @@ +# 建模反模式 + +## P0 — 必须立即修复 + +### AP-01:事实表混合粒度 +**表现:** 一张表里同时有订单级和订单行级数据。 +**后果:** 聚合结果直接错误,而且错误不容易发现——SUM 会多算,COUNT 看起来正常。 +**修复:** 拆成两张事实表,各自粒度明确。粒度拿不准时选最细。 + +### AP-02:ADS 直接读 ODS +**表现:** 应用层 SQL 里 `FROM ods_xxx`。 +**后果:** 绕过了所有清洗和质量保障。ODS 里的脏数据、重复数据、格式问题全部暴露给下游。 +**修复:** 断掉这条链路,重写为读 DWD 或 DWS。如果确实有明细数据需求,在 ADS 层建视图引用 DWD。 + +### AP-03:指标口径分裂 +**表现:** "支付金额"在 3 个 ADS 表里各算一遍,SQL 还不一样。 +**后果:** 三个报表三个数,业务方不再信任数据。 +**修复:** 下沉到 DWS 层统一计算。但真正的难点不是技术——是让业务方同意用哪个口径。先搞定口径共识,再改 SQL。 + +### AP-04:事实表无主键 +**表现:** `SELECT COUNT(*) != COUNT(DISTINCT 业务键)`。 +**后果:** 重跑 ETL 后数据翻倍,指标多计。而且没人能发现问题,因为没有唯一性校验。 +**修复:** 加主键约束(或至少加唯一性测试)。dbt 里配 `unique + not_null` 测试。 + +## P1 — 短期内必须处理 + +### AP-05:数据烟囱 +**表现:** 相同业务逻辑在多个 ADS 表中重复实现。 +**后果:** 资源浪费,口径不一致(不是"可能"不一致,是"迟早"不一致)。 +**修复:** 3 个以上下游共用的计算,必须下沉到 DWS。 + +### AP-06:ODS 层做了业务逻辑 +**表现:** ODS 的 ETL 里写了 CASE WHEN、类型转换、关联计算。 +**后果:** 丧失了数据可追溯性。出了问题无法和源系统对账。 +**修复:** ODS 只做格式转换和去重。所有业务逻辑移到 DWD。 + +### AP-07:SCD 没处理 +**表现:** 用户从"普通会员"变成"金卡会员"后,所有历史订单的用户等级字段也跟着变了。 +**后果:** 历史分析失真。"去年金卡会员的订单金额"这个数字毫无意义。 +**修复:** 需要历史追溯的维度属性用 SCD2(拉链表)。维度数据量小的话,全量快照也行。 + +### AP-08:万能宽表 +**表现:** 一张表 500+ 字段,混合了 10 个不同粒度的指标。 +**后果:** 维护极难,没人敢改(怕影响其他字段),字段含义模糊(新来的人看不懂)。 +**修复:** 核心 + 扩展分离。核心字段(20-30 个)稳定不变,扩展字段独立维护。或者按业务过程拆表。 + +### AP-09:SELECT * +**表现:** `INSERT INTO dwd_xxx SELECT * FROM ods_xxx`。 +**后果:** 下游依赖了不该依赖的字段,源系统改个字段名就大面积报错。列裁剪优化也失效了。 +**修复:** 显式声明每个字段。哪怕只是透传。 + +### AP-10:维度碎片化 +**表现:** 两个团队各维护一份 dim_user,字段含义还不一样。 +**后果:** 跨域分析不可能。交易域的"活跃用户"和流量域的"活跃用户"是两个数。 +**修复:** 建立公共维度层,每个维度只有一张主维表。至少先统一 dim_user 和 dim_product。 + +## P2 — 建议优化 + +### AP-11:NULL 语义不明 +**表现:** `discount_amt = NULL` 不知道是"没折扣"还是"数据缺失"。 +**修复:** "没折扣"用 0,"数据缺失"用 NULL。写进字段注释。 + +### AP-12:分区粒度过细 +**表现:** 按小时 + 业务线分区,产生 10 万个小文件。 +**修复:** 根据实际查询模式设计分区。大多数场景按天分区就够了。 + +### AP-13:SELECT 前不聚合 +**表现:** 先 JOIN 亿级表再 GROUP BY,而不是先聚合再 JOIN。 +**修复:** Filter early, aggregate early, join late。 + +### AP-14:命名无意义 +**表现:** `dw_data_new_v2_final_20240101`。 +**修复:** 遵循命名规范。没有规范?先建规范再建表。 + +### AP-15:硬编码表名 +**表现:** dbt 模型里 `FROM prod.dwd.dwd_order_d`。 +**修复:** `FROM {{ ref('dwd_order_d') }}`。保持血缘。 + +### AP-16:缺少增量策略 +**表现:** 百亿级事实表每次全量重算。 +**修复:** 设计增量分区策略。只处理新增和变更数据。 + +### AP-17:无数据质量监控 +**表现:** 数据质量问题被下游用户发现,而不是数据团队。 +**修复:** 至少配置行数波动告警(±30%)、NULL 率监控、主键唯一性检查。 + +### AP-18:事实表存维度属性 +**表现:** fct_order 里存了 user_city、item_brand。 +**后果:** 维度变更后,要么历史数据失真,要么要重刷大量分区。 +**修复:** 维度属性放 dim 表,事实表只存外键。DWD 层做维度退化是合理的,但退化的是查询必需的属性。 + +### AP-19:过深的 JOIN 链 +**表现:** 7 层 JOIN 才能出报表。 +**修复:** DWD 层做维度退化,DWS 层做宽表预关联。 + +### AP-20:维度表没有代理键 +**表现:** 事实表直接用业务自然键做外键。 +**修复:** 维度表加代理键(sk)。业务键变更时代理键不受影响。 + +## 排查优先级 + +看到数仓出问题时,按这个顺序排查: +1. 主键是否唯一(AP-04)— 不唯一 = 数据不可信 +2. 粒度是否一致(AP-01)— 不一致 = 聚合一定错 +3. 有没有跨层引用(AP-02)— 有 = 质量没保障 +4. 指标口径是否统一(AP-03)— 不统一 = 数字打架 +5. 其余的按 P1 → P2 逐步清理 diff --git a/skills/data-warehouse-modeling/references/bus-matrix.md b/skills/data-warehouse-modeling/references/bus-matrix.md new file mode 100644 index 0000000..4fde9e6 --- /dev/null +++ b/skills/data-warehouse-modeling/references/bus-matrix.md @@ -0,0 +1,92 @@ +# 总线矩阵 + +## 为什么需要总线矩阵 + +总线矩阵解决一个核心问题:**哪些业务过程共用哪些维度。** + +没有总线矩阵,每个团队各建各的维度表,`dim_user` 在交易域和流量域是两张表,跨域分析做不了。有了总线矩阵,所有共用的维度一目了然,公共维度层就知道该建哪些表。 + +## 怎么画 + +### Step 1:列业务过程 +把主题域内的原子业务动作列出来。电商交易域:浏览、加购、下单、支付、退款。 + +### Step 2:列维度 +把所有业务过程可能用到的分析角度列出来。用户、商品、时间、地域、渠道、促销。 + +### Step 3:填矩阵 +每个交叉点标上 ●(使用)或留空(不使用)。 + +``` + │ 用户 │ 商品 │ 时间 │ 地域 │ 渠道 │ 促销 │ +───────────┼──────┼──────┼──────┼──────┼──────┼──────┤ +商品浏览 │ ● │ ● │ ● │ ● │ ● │ │ +加购 │ ● │ ● │ ● │ │ ● │ │ +下单 │ ● │ ● │ ● │ ● │ ● │ ● │ +支付 │ ● │ ● │ ● │ ● │ ● │ ● │ +退款 │ ● │ ● │ ● │ │ ● │ │ +``` + +### Step 4:识别一致性维度 +出现 ● 超过 3 次的维度,必须建为公共维度(`dim_` 表)。 + +## 矩阵和 DWS 层的关系 + +矩阵中的每个 ● 交叉点,对应 DWS 层可能需要的一张汇总表。 + +``` +业务过程:支付成功 × 维度:用户 + 商品 + 日期 +→ dws_trade_pay_user_item_1d +``` + +但不是每个交叉点都要建表。只建下游确实在消费的。 + +## 维度表 DDL 示例 + +### dim_user(SCD2 拉链表) + +```sql +-- 维度表必须用代理键做主键 +CREATE TABLE dim_user ( + user_sk BIGINT COMMENT '代理键(数仓自增)', + user_id BIGINT COMMENT '业务键(源系统)', + user_name STRING COMMENT '用户名', + phone_masked STRING COMMENT '手机号(脱敏后)', + gender STRING COMMENT '性别', + age_group STRING COMMENT '年龄段', + user_level STRING COMMENT '用户等级', + province_code STRING COMMENT '省份编码', + city_code STRING COMMENT '城市编码', + start_dt DATE COMMENT '生效日期', + end_dt DATE COMMENT '失效日期(9999-12-31 = 当前有效)', + is_current TINYINT COMMENT '当前行:1 是 0 否', + etl_dt DATE COMMENT 'ETL 日期' +) COMMENT '用户维度(SCD2 拉链表)' +PARTITIONED BY (dt STRING); +``` + +### dim_date(静态预生成) + +```sql +-- 时间维度不需要 SCD,预生成 10 年的数据 +CREATE TABLE dim_date ( + date_key INT COMMENT 'YYYYMMDD', + full_date DATE COMMENT '完整日期', + year INT, + quarter INT, + month INT, + week_of_year INT, + day_of_week INT COMMENT '1=周一', + is_weekend TINYINT, + is_holiday TINYINT COMMENT '法定节假日', + holiday_name STRING, + fiscal_year INT COMMENT '财年', + fiscal_quarter INT +) COMMENT '时间维度(预生成 10 年)'; +``` + +## 常见错误 + +1. **维度列太多** — 列 30 个维度没用。只列核心的(5-8 个),业务特有维度在子域级别管理 +2. **业务过程粒度太粗** — "交易"不是业务过程,"下单"才是 +3. **画完就完了** — 总线矩阵是活文档,业务新增过程时要更新 diff --git a/skills/data-warehouse-modeling/references/cloud-platform-practices.md b/skills/data-warehouse-modeling/references/cloud-platform-practices.md new file mode 100644 index 0000000..4816a81 --- /dev/null +++ b/skills/data-warehouse-modeling/references/cloud-platform-practices.md @@ -0,0 +1,183 @@ +# Cloud Platform DW Best Practices / 云平台数仓最佳实践 + +## Platform Comparison / 平台对比 + +| Feature | Snowflake | BigQuery | Databricks | Redshift | +|---------|-----------|----------|------------|----------| +| **Architecture** | Shared-data, separate compute | Serverless, columnar | Lakehouse (Delta Lake) | Shared-nothing MPP | +| **Storage** | Cloud storage (S3/GCS/Azure) | Colossus (proprietary) | Delta Lake (open format) | Local + S3 (Redshift Spectrum) | +| **Compute** | Virtual Warehouses (multi-cluster) | Slots (on-demand/flat-rate) | Clusters / SQL Warehouses | RA3 nodes | +| **Scaling** | Auto-scale multi-cluster VW | Auto-scale slots | Auto-scale clusters | Elastic resize / concurrency scaling | +| **Pricing** | Per-second compute + storage | On-demand bytes scanned or flat-rate | DBUs + storage | Per-node-hour + storage | +| **Semi-structured** | VARIANT (JSON/Parquet/Avro) | Native JSON/ARRAY/STRUCT | Full Parquet/JSON/Avro | SUPER type (JSON) | +| **Streaming** | Snowpipe + Streaming API | Storage Write API | Structured Streaming + Auto Loader | Kinesis / MSK integration | +| **Time Travel** | Up to 90 days | Up to 7 days | Up to 30 days (Delta) | Not native | +| **Data Sharing** | Snowflake Data Marketplace | Analytics Hub | Delta Sharing | Data Exchange | +| **Governance** | RBAC + row-level security + masking | IAM + column-level security + policy tags | Unity Catalog | IAM + row-level security | + +## Snowflake Best Practices + +### Layering with Schemas +```sql +-- Use schemas for layering instead of separate databases +CREATE SCHEMA raw; -- Bronze / ODS +CREATE SCHEMA cleansed; -- Silver / DWD +CREATE SCHEMA analytics; -- Gold / DWS+ADS + +-- Use dynamic tables for incremental materialization +CREATE OR REPLACE DYNAMIC TABLE dwd_trade_order_detail + TARGET_LAG = '1 hour' + WAREHOUSE = 'ETL_WH' + AS + SELECT + o.order_id, + o.user_id, + o.product_id, + dim.category_name, + dim.brand_name, + o.order_amt, + o.pay_amt, + o.order_time + FROM raw.ods_orders o + LEFT JOIN analytics.dim_product dim ON o.product_id = dim.product_id + WHERE o.order_status IN ('PAID', 'SHIPPED', 'COMPLETED'); +``` + +### Key Patterns +- **Virtual Warehouse sizing**: XS for dev, S/M for ETL, L/XL for heavy BI queries +- **Clustering keys**: Use on large tables queried by specific columns (e.g., `CLUSTER BY (order_date, region)`) +- **Zero-copy cloning**: For dev/test environments and time-travel queries +- **Snowpipe**: For continuous micro-batch loading (< 1 min latency) +- **Streams + Tasks**: CDC pattern for incremental ETL + +### Cost Optimization +- Auto-suspend warehouses after 5 min idle +- Use resource monitors with credit quotas +- Separate ETL and BI warehouses +- Use result caching (automatic for repeated queries) + +## BigQuery Best Practices + +### Layering with Datasets +```sql +-- Use datasets for layering +CREATE SCHEMA `project.raw`; -- Bronze / ODS +CREATE SCHEMA `project.cleansed`; -- Silver / DWD +CREATE SCHEMA `project.analytics`; -- Gold / DWS+ADS + +-- Use scheduled queries or dbt for transformation +-- Example: Materialized view for DWS +CREATE MATERIALIZED VIEW `project.analytics.dws_daily_sales` + AS + SELECT + order_date, + region, + COUNT(DISTINCT order_id) AS order_cnt, + SUM(pay_amt) AS total_pay_amt + FROM `project.cleansed.dwd_trade_order_detail` + GROUP BY order_date, region; +``` + +### Key Patterns +- **Partitioning**: Always partition by `DATE` or `TIMESTAMP` column (required for large tables) +- **Clustering**: Cluster on frequently filtered/joined columns (up to 4 columns) +- **Slot allocation**: Use flat-rate for predictable workloads, on-demand for bursty +- **Streaming insert**: Use `storage.writeApi` for real-time (< 1 sec latency) +- **BigQuery Omni**: For multi-cloud data access + +### Cost Optimization +- Use clustering to reduce bytes scanned +- Use `--maximum_bytes_billed` flag for query cost caps +- Use logical views for lightweight transforms +- Scheduled queries for periodic ETL (free scheduling, pay only for queries) + +## Databricks Best Practices + +### Medallion Architecture with Delta Lake +```sql +-- Bronze: Raw ingestion +CREATE TABLE bronze.orders ( + raw_data STRING, + ingestion_time TIMESTAMP, + source_file STRING +) USING DELTA +PARTITIONED BY (ingestion_date DATE); + +-- Silver: Cleaned and conformed +CREATE TABLE silver.orders ( + order_id STRING, + user_id BIGINT, + product_id BIGINT, + order_amt DECIMAL(18,2), + pay_amt DECIMAL(18,2), + order_time TIMESTAMP, + etl_time TIMESTAMP +) USING DELTA +PARTITIONED BY (order_date DATE) +TBLPROPERTIES ( + 'delta.autoOptimize.autoCompact' = 'true', + 'delta.autoOptimize.optimizeWrite' = 'true' +); + +-- Gold: Business aggregates +CREATE TABLE gold.daily_sales ( + order_date DATE, + region STRING, + category STRING, + order_cnt BIGINT, + total_pay_amt DECIMAL(18,2), + unique_buyers BIGINT +) USING DELTA +PARTITIONED BY (order_date DATE); +``` + +### Key Patterns +- **Auto Loader**: For incremental file ingestion (`cloudFiles` format) +- **Z-Order**: Optimize clustering on frequently queried columns (`OPTIMIZE table ZORDER BY (user_id, date)`) +- **Liquid Clustering** (DBSQL/Spark 3.5+): `CLUSTER BY` for self-tuning layout +- **Change Data Feed**: `delta.enableChangeDataFeed` for CDC-based incremental processing +- **Unity Catalog**: Centralized governance, lineage, and access control + +### Cost Optimization +- Use spot instances for non-critical ETL +- Photon engine for SQL workloads (2-5x faster, but costs more DBUs) +- Auto-terminate clusters after 15 min idle +- Use shared clusters for interactive, job clusters for scheduled ETL + +## Redshift Best Practices + +### Key Patterns +- **Sort keys**: Choose based on query patterns (COMPOUND for range, INTERLEAVED for multi-dimensional) +- **Distribution style**: KEY for frequent joins, ALL for small dimension tables, EVEN for no clear pattern +- **Materialized views**: For pre-computed aggregates +- **Redshift Spectrum**: Query S3 data directly without loading +- **Concurrency scaling**: Auto-add capacity for concurrent queries + +### Layering Pattern +```sql +-- Use schemas for layering +CREATE SCHEMA raw; -- ODS +CREATE SCHEMA staging; -- DWD +CREATE SCHEMA analytics; -- DWS+ADS+DIM + +-- Distribution and sort key example +CREATE TABLE analytics.fct_orders ( + order_sk BIGINT DISTKEY SORTKEY, + order_id VARCHAR(50), + user_sk BIGINT, + product_sk BIGINT, + order_amt DECIMAL(18,2), + order_date DATE +) DISTSTYLE KEY; +``` + +## Cross-Platform SQL Migration Notes + +| Concept | Snowflake | BigQuery | Databricks/Spark | Redshift | +|---------|-----------|----------|------------------|----------| +| Upsert | `MERGE INTO` | `MERGE` | `MERGE INTO` | `MERGE INTO` | +| Incremental | Dynamic Tables / Streams | Scheduled Queries / dbt | Change Data Feed / Auto Loader | Materialized Views | +| JSON parse | `PARSE_JSON()` | Native `JSON_QUERY` | `from_json()` | `JSON_PARSE_*` | +| Arrays | `ARRAY_AGG` | Native `ARRAY` | `collect_list` / `array` | `LISTAGG` / `SUPER` | +| Time travel | `AT(TIMESTAMP => ...)` | `FOR SYSTEM_TIME AS OF` | `VERSION AS OF` | Not native | +| Row masking | Dynamic Data Masking | Policy Tags | Unity Catalog Column Masking | Dynamic Data Masking | diff --git a/skills/data-warehouse-modeling/references/data-governance.md b/skills/data-warehouse-modeling/references/data-governance.md new file mode 100644 index 0000000..406d5ea --- /dev/null +++ b/skills/data-warehouse-modeling/references/data-governance.md @@ -0,0 +1,195 @@ +# Data Governance & Compliance / 数据治理与合规 + +## Regulatory Frameworks / 合规框架 + +| Regulation | Region | Key Requirements for DW / 对数仓的要求 | +|-----------|--------|--------------------------------------| +| **GDPR** | EU/EEA | Right to erasure, data minimization, consent tracking, DPIA | +| **CCPA/CPRA** | California, US | Consumer rights, opt-out, data disclosure | +| **PIPL** | China | Consent, data localization, cross-border transfer assessment | +| **LGPD** | Brazil | Similar to GDPR, data protection officer required | +| **HIPAA** | US Healthcare | PHI encryption, access audit, minimum necessary | +| **SOX** | US Public Companies | Financial data accuracy, audit trail, internal controls | +| **PCI DSS** | Global (card payments) | Cardholder data encryption, access control, monitoring | + +## Data Classification / 数据分级 + +### Standard Classification / 标准分级 + +| Level | Name | Examples | Controls / 控制措施 | +|-------|------|----------|-------------------| +| **L1** | Public / 公开 | Press releases, public docs | No restrictions | +| **L2** | Internal / 内部 | Org charts, internal reports | Internal access only | +| **L3** | Confidential / 机密 | Revenue figures, customer counts | Role-based access | +| **L4** | Sensitive / 敏感 | Email, phone, address | Masking + RBAC + audit | +| **L5** | Restricted / 受限 | SSN, passwords, card numbers | Encryption at rest + in transit + column masking + full audit | + +### Implementation by Platform / 各平台实现 + +**Snowflake:** +```sql +-- Dynamic Data Masking +CREATE MASKING POLICY email_mask AS (val STRING) RETURNS STRING -> + CASE WHEN CURRENT_ROLE() IN ('ADMIN') THEN val + ELSE REGEXP_REPLACE(val, '(.{2}).+(@.+)', '\\1***\\2') + END; + +ALTER TABLE dim_user MODIFY COLUMN email SET MASKING POLICY email_mask; + +-- Row-Level Security +CREATE ROW ACCESS POLICY region_access AS (region STRING) RETURNS BOOLEAN -> + CURRENT_ROLE() = 'ADMIN' OR region = CURRENT_REGION(); + +ALTER TABLE fct_sales ADD ROW ACCESS POLICY region_access ON (region); +``` + +**BigQuery:** +```sql +-- Column-level policy tags (via Data Catalog) +-- Tag columns with policy tags: e.g., "PII_EMAIL", "PII_PHONE" +-- Then assign IAM roles for each tag + +-- Row-level security +CREATE ROW ACCESS POLICY us_sales ON analytics.fct_sales + GRANT TO ("group:us-team@example.com") + FILTER USING (region = 'US'); +``` + +**Databricks (Unity Catalog):** +```sql +-- Column masking +CREATE FUNCTION mask_email(email STRING) + RETURNS STRING + RETURN CASE + WHEN is_member('admin') THEN email + ELSE CONCAT(SUBSTRING(email, 1, 2), '***', SUBSTRING(email, INSTR(email, '@'))) + END; + +ALTER TABLE dim_user ALTER COLUMN email SET MASK mask_email; + +-- Row-level security +CREATE FUNCTION region_filter(region STRING) + RETURNS BOOLEAN + RETURN is_member('admin') OR region = current_user_region(); + +ALTER TABLE fct_sales SET ROW FILTER region_filter ON (region); +``` + +## PII Handling in DW / DW 中的 PII 处理 + +### Data Minimization / 数据最小化 + +| Strategy / 策略 | When to Use / 场景 | Example / 示例 | +|-----------------|-------------------|----------------| +| **Drop** | Not needed downstream / 下游不需要 | Remove SSN from analytical tables | +| **Mask** | Format preserved but redacted / 格式保留 | `j***@gmail.com` | +| **Hash** | Linkage needed, value hidden / 需关联但隐藏值 | `SHA2(email, 256)` | +| **Tokenize** | Reversible for authorized systems / 授权可逆 | Token vault → `tok_abc123` | +| **Aggregate** | Only statistics needed / 仅需统计 | Store `age_group` instead of `birth_date` | +| **Generalize** | Reduce precision / 降低精度 | `city` instead of `full_address` | + +### Right to Erasure / 删除权实现 + +```sql +-- Pattern 1: Soft delete flag (easy but data remains) +ALTER TABLE dim_user ADD COLUMN is_erased BOOLEAN DEFAULT FALSE; +UPDATE dim_user SET is_erased = TRUE, user_name = '[ERASED]', email = NULL + WHERE user_id = :target_user; + +-- Pattern 2: Anonymization (GDPR-compliant, data remains for analytics) +UPDATE dim_user SET + user_name = 'ANON_' || user_id, + email = 'anon_' || user_id || '@erased.com', + phone = NULL, + birth_date = NULL +WHERE user_id = :target_user; + +-- Pattern 3: Hard delete + cascade (thorough but complex) +DELETE FROM dim_user WHERE user_id = :target_user; +-- Must propagate to all fact tables (replace user_id with NULL or -1 sentinel) +``` + +## Data Lineage & Cataloging / 数据血缘与编目 + +### Lineage Tracking / 血缘追踪 + +| Tool | Platform | Key Feature | +|------|----------|-------------| +| **dbt** | Cross-platform | Auto-generated DAG lineage from `ref()` calls | +| **Unity Catalog** | Databricks | Column-level lineage, automatic | +| **Information Schema** | Snowflake | `QUERY_HISTORY` + object dependencies | +| **Data Catalog** | BigQuery | Automatic lineage from scheduled queries | +| **Apache Atlas** | On-prem/Hadoop | Tag-based classification, lineage REST API | +| **DataHub** | Open-source | Metadata platform, lineage, ownership | +| **OpenLineage** | Open standard | Cross-tool lineage standard (Marquez) | + +### Required Metadata per Table / 每表必备元数据 + +```yaml +# Example: dbt schema.yml +models: + - name: fct_orders + description: "Order fact table. Grain: one row per order line item." + meta: + owner: "data-platform-team" + sla: "T+1 08:00 UTC" + data_classification: "L3" + refresh_frequency: "daily" + contains_pii: false + columns: + - name: order_id + description: "Unique order identifier (natural key)" + tests: [unique, not_null] + meta: + classification: "L2" + - name: user_id + description: "Foreign key to dim_user" + meta: + classification: "L4" # Can identify a person + masking_policy: "hash_policy" +``` + +## Data Quality Framework / 数据质量框架 + +### Quality Dimensions / 质量维度 + +| Dimension / 维度 | Check / 检查 | Example | +|-----------------|-------------|---------| +| **Completeness** / 完整性 | NULL rate threshold | `null_rate(email) < 5%` | +| **Uniqueness** / 唯一性 | PK uniqueness | `unique(order_id)` | +| **Validity** / 有效性 | Range/enum check | `pay_amt >= 0 AND pay_amt < 1000000` | +| **Timeliness** / 时效性 | Freshness SLA | `data_freshness < 24h` | +| **Consistency** / 一致性 | Cross-table check | `SUM(orders) = COUNT(DISTINCT order_id)` | +| **Accuracy** / 准确性 | Source reconciliation | `DW_count - source_count < threshold` | + +### Monitoring Implementation / 监控实现 + +```sql +-- Snowflake: Data quality check view +CREATE VIEW dq.fct_orders_checks AS +SELECT + CURRENT_TIMESTAMP() AS check_time, + 'fct_orders' AS table_name, + COUNT(*) AS total_rows, + COUNT(DISTINCT order_id) AS unique_orders, + SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END) AS null_user_cnt, + SUM(CASE WHEN pay_amt < 0 THEN 1 ELSE 0 END) AS negative_amt_cnt, + AVG(pay_amt) AS avg_pay_amt +FROM analytics.fct_orders +WHERE order_date = CURRENT_DATE() - 1; +``` + +## Compliance Checklist / 合规检查清单 + +### For Every New Table / 每张新表必检 + +- [ ] Data classification level assigned / 已分配数据分级 +- [ ] PII columns identified and masked/hashed / PII 列已识别并脱敏 +- [ ] Access control configured (RBAC) / 已配置访问控制 +- [ ] Audit logging enabled / 已启用审计日志 +- [ ] Retention policy defined / 已定义保留策略 +- [ ] Data lineage documented / 已记录数据血缘 +- [ ] Owner and SLA declared / 已声明负责人和 SLA +- [ ] Data quality checks configured / 已配置数据质量检查 +- [ ] Cross-border data transfer assessed (if applicable) / 已评估跨境传输 +- [ ] Consent status verified (for personal data) / 已验证同意状态 diff --git a/skills/data-warehouse-modeling/references/dbt-practices.md b/skills/data-warehouse-modeling/references/dbt-practices.md new file mode 100644 index 0000000..b25b02b --- /dev/null +++ b/skills/data-warehouse-modeling/references/dbt-practices.md @@ -0,0 +1,192 @@ +# dbt 工程化建模完整指南 + +## 项目目录结构(标准规范) + +``` +models/ +├── staging/ # stg_: 1:1 映射源表 +│ ├── ecommerce/ +│ │ ├── _ecommerce__sources.yml # 声明源表 +│ │ ├── stg_ecommerce__orders.sql +│ │ └── stg_ecommerce__users.sql +│ └── payment/ +│ └── stg_payment__transactions.sql +├── intermediate/ # int_: 可复用中间逻辑 +│ ├── int_orders_enriched.sql +│ └── int_user_lifetime_value.sql +└── marts/ # 业务域数据集市 + ├── core/ + │ ├── fct_orders.sql # 事实表 + │ └── dim_users.sql # 维度表 + ├── finance/ + │ └── fct_revenue.sql + └── marketing/ + └── fct_campaign_performance.sql +``` + +## 命名规范(dbt 标准) + +``` +stg___.sql # staging: 源系统__实体 +int_.sql # intermediate: 描述性名称 +fct_.sql # facts: 事件或过程名 +dim_.sql # dimensions: 实体名 +rpt_.sql # reports: 报表(可选层) +``` + +## 物化策略选择 + +```yaml +# dbt_project.yml +models: + myproject: + staging: + +materialized: view # staging 用视图,让仓库管理刷新 + intermediate: + +materialized: ephemeral # 中间层内嵌,不产生实体表 + marts: + core: + +materialized: table # 核心集市用表,查询性能最优 + +materialized: incremental # 大表用增量 +``` + +**选择原则:** +- `view`:轻量变换,不需要持久化,数据量小 +- `table`:稳定宽表,查询频繁,需要最优性能 +- `incremental`:大数据量事件表,只处理新增/变更行 +- `ephemeral`:纯中间逻辑,不需要独立存储 + +## 增量模型最佳实践 + +```sql +-- fct_orders.sql +{{ + config( + materialized='incremental', + unique_key='order_id', + incremental_strategy='merge', + partition_by={ + "field": "order_date", + "data_type": "date", + "granularity": "day" + } + ) +}} + +SELECT + order_id, + user_id, + order_amount, + order_date, + status +FROM {{ ref('stg_ecommerce__orders') }} + +{% if is_incremental() %} + WHERE order_date >= (SELECT MAX(order_date) FROM {{ this }}) + OR updated_at > (SELECT MAX(updated_at) FROM {{ this }}) +{% endif %} +``` + +## 数据测试规范 + +```yaml +# schema.yml +models: + - name: fct_orders + description: "订单事实表,每行代表一笔订单" + columns: + - name: order_id + description: "订单唯一标识" + tests: + - unique # 主键唯一 + - not_null # 主键非空 + - name: user_id + tests: + - not_null + - relationships: # 外键引用完整性 + to: ref('dim_users') + field: user_id + - name: status + tests: + - accepted_values: # 枚举值检查 + values: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'] + - name: order_amount + tests: + - dbt_utils.expression_is_true: # 自定义断言 + expression: ">= 0" +``` + +## 文档化规范 + +```yaml +# schema.yml - 完整文档示例 +models: + - name: dim_users + description: > + 用户维度表,包含用户基础属性。 + SCD Type 2 处理:用户等级、地址变更均保留历史记录。 + 数据来源:用户中台 user_service 数据库。 + meta: + owner: "data-platform-team" + sla: "T+1 08:00" + data_sensitivity: "PII" + columns: + - name: user_sk + description: "用户代理键,数仓自增,用于跨表关联" + - name: user_id + description: "用户自然键,来源于用户中台" + - name: is_current + description: "SCD2当前记录标记:1=当前有效,0=历史记录" +``` + +## ref() 和 source() 使用规范 + +```sql +-- ✅ 正确:通过 ref() 引用其他模型 +SELECT * FROM {{ ref('stg_ecommerce__orders') }} + +-- ✅ 正确:通过 source() 引用原始数据 +SELECT * FROM {{ source('ecommerce', 'raw_orders') }} + +-- ❌ 错误:硬编码表名(破坏血缘关系和跨环境部署) +SELECT * FROM prod_db.dwd.dwd_trade_order_detail_d +``` + +## CI/CD 集成 + +```yaml +# .github/workflows/dbt_ci.yml +- name: Run dbt build (only changed models) + run: | + dbt build --select state:modified+ --defer --state ./prod-artifacts + +- name: Run dbt test + run: | + dbt test --select state:modified+ +``` + +**关键命令:** +```bash +# 只构建变更的模型及其下游 +dbt build --select state:modified+ + +# 生成文档 +dbt docs generate && dbt docs serve + +# 增量构建大表 +dbt run --select fct_orders --full-refresh # 首次或修复 + +# 查看血缘图 +dbt ls --select fct_orders+ # 所有下游 +dbt ls --select +fct_orders # 所有上游 +``` + +## dbt 与分层架构的映射 + +| dbt 层 | 数仓层 | 说明 | +|--------|--------|------| +| sources | ODS | 声明原始表,不做变换 | +| staging | ODS→DWD | 轻量清洗,1:1 映射 | +| intermediate | DWD | 可复用业务逻辑 | +| marts/core (fct_/dim_) | DWS/DIM | 标准事实维度 | +| marts/{domain} | ADS | 面向业务域的汇总 | diff --git a/skills/data-warehouse-modeling/references/dw-doc-standards.md b/skills/data-warehouse-modeling/references/dw-doc-standards.md new file mode 100644 index 0000000..3841684 --- /dev/null +++ b/skills/data-warehouse-modeling/references/dw-doc-standards.md @@ -0,0 +1,142 @@ +# 数仓文档规范 + +## 写文档的实际目的 + +数仓文档不是为了走流程,是为了: +1. 新来的人 3 天内能上手,不用追着老人问 +2. 半年后你自己还记得当时为什么这么设计 +3. 指标口径争议时有据可查 + +## 排版规则 + +就三条,做不到别的至少做到这三条: + +1. **中英文之间加空格** — `使用 Flink 进行 ETL` 不是 `使用Flink进行ETL` +2. **中文用全角标点** — `注意:` 不是 `注意:` +3. **术语首次出现标注中英对照** — `ODS(Operational Data Store,操作数据层)` + +## 数仓设计文档模板 + +文档不长,但每个字段都要写清楚业务含义。 + +```markdown +# {主题域} 数仓设计文档 + +## 1. 基本信息 +| 项目 | 内容 | +|------|------| +| 主题域 | {域名} | +| 版本 | v1.0 | +| 作者 | {作者} | +| 日期 | {日期} | + +## 2. 业务背景 +这个主题域覆盖什么业务?为什么要建这些表?给谁用? + +## 3. 数据来源 +| 数据源 | 类型 | 同步方式 | 频率 | +|--------|------|---------|------| +| {源系统} | MySQL/日志/API | Canal/Sqoop/Flink | 实时/T+1 | + +## 4. 主题域划分 +### 业务过程列表 +| 业务过程 | 事实表名 | 粒度(一行代表什么) | +|----------|---------|-------------------| +| {过程} | {表名} | {具体描述} | + +### 总线矩阵 +{业务过程 × 维度} + +## 5. 模型设计 + +### ODS 层 +{表 DDL + 字段注释} + +### DWD 层 +{表 DDL + 粒度声明(block comment)+ 清洗规则} + +### DIM 层 +{维度 DDL + SCD 策略说明} + +### DWS 层 +{汇总 DDL + 指标口径} + +### ADS 层 +{应用 DDL + 消费场景} + +## 6. 指标定义 +| 指标 | 类型 | 口径 | 公式 | 周期 | 粒度 | +|------|------|------|------|------|------| +| {名} | 原子/派生/复合 | {一句话说清楚} | {SQL 或算式} | {天/周/月} | {维度} | + +## 7. 数据质量规则 +| 表 | 规则 | 阈值 | 告警级别 | +|----|------|------|---------| +| {表名} | 唯一性/非空/范围/枚举 | {值} | P1/P2/P3 | +``` + +## 指标定义文档模板 + +指标定义文档的核心是**口径描述**。写不清楚口径,等于没写。 + +```markdown +## 指标:{名称} + +| 项目 | 内容 | +|------|------| +| 英文名 | {metric_root_name} | +| 类型 | 原子 / 派生 / 复合 | +| 主题域 | {域} | +| 负责人 | {谁对口径负责} | + +### 口径 +用一句话说清楚这个指标算的是什么。如果有歧义,把可能的理解都列出来,标注"本定义取哪种"。 + +**原子指标示例:** 支付成功的订单金额。口径:`SUM(pay_amt) WHERE order_status = 'PAID'`。 +不含退款、不含优惠券抵扣、统计截止时间点为 T+1 00:00 UTC。 + +**派生指标示例:** 近 7 天母婴品类按省份的支付金额。 += 支付金额(原子指标)+ 母婴品类(业务限定)+ 近 7 天(时间窗口)+ 省份(统计粒度)。 + +### 质量规则 +- 合理范围:{如 `0 ≤ 日 GMV ≤ 10 亿`} +- 日环比波动:不超过 ±30% +- NULL 处理:NULL → 0 + +### 变更记录 +| 日期 | 变更 | 人 | +|------|------|----| +``` + +## 建模评审清单 + +评审不是走过场。按这个顺序检查: + +### 第一步:分层 +- [ ] 分层是否合理 +- [ ] 有没有跨层引用(ADS 直接读 ODS/DWD) +- [ ] 有没有反向依赖 + +### 第二步:粒度 +- [ ] 事实表粒度是否明确声明(写在 DDL 上方) +- [ ] 有没有混合粒度 + +### 第三步:命名 +- [ ] 表名是否符合规范 +- [ ] 字段名是否用了词根词典 +- [ ] 增量标记是否正确 + +### 第四步:维度 +- [ ] 维度是否在 DIM 层统一管理 +- [ ] SCD 策略是否合理 +- [ ] 有没有维度碎片化 + +### 第五步:指标 +- [ ] 原子指标口径是否唯一 +- [ ] 派生指标是否可追溯 +- [ ] 有没有重复计算 + +### 第六步:治理 +- [ ] 数据分级是否标注 +- [ ] PII 字段是否脱敏 +- [ ] 负责人和 SLA 是否声明 diff --git a/skills/data-warehouse-modeling/references/layer-architecture.md b/skills/data-warehouse-modeling/references/layer-architecture.md new file mode 100644 index 0000000..ca978a6 --- /dev/null +++ b/skills/data-warehouse-modeling/references/layer-architecture.md @@ -0,0 +1,120 @@ +# 分层架构 + +## 为什么分层 + +不分层的数仓,三个月后就会出现这些问题: +- 同一个指标,5 个表各算各的,数字对不上 +- 改一个源表字段,10 个下游报表全挂 +- 新来的人看不懂表结构,只能找原作者问 + +分层不是架构师的自嗨,是控制复杂度的唯一手段。 + +## ODS — 贴源层 + +**职责:** 原样保留源数据。 + +**该做的:** +- 格式转换(Binlog JSON → 列式存储) +- 去重(业务库 Binlog 重复消费) +- 按天分区 + +**不该做的:** +- 任何业务逻辑(CASE WHEN、枚举映射、关联计算) +- 字段删减 + +ODS 层的价值是"出问题时可以和源系统对账"。一旦做了业务逻辑,这个价值就没了。 + +**保留周期:** 3-6 个月。长了占空间,短了对不了账。 + +## DWD — 明细层 + +**职责:** 清洗后的标准化明细数据,保持最细粒度。 + +**核心工作:** +1. **清洗** — 去重、空值处理、枚举映射、格式统一 +2. **维度退化** — 高频维度属性退化到事实表中。退化的判断标准:查询时每次都要 JOIN 这个字段。不是"可能有用"的都退化 +3. **粒度声明** — 每张表上方必须有 block comment 写明"一行代表什么" + +**粒度声明的正确写法:** +```sql +-- 一行代表:一笔订单中的一个商品行(同一订单可能有多行) +CREATE TABLE dwd_trade_order_detail_di (...) +``` + +**粒度声明的错误写法:** +```sql +-- 订单粒度 +CREATE TABLE dwd_trade_order_detail_di (...) +``` + +## DWS — 汇总层 + +**职责:** 按主题域 + 统计粒度聚合。 + +**设计原则:** +- 宽表设计,一行代表一个实体在某个粒度上的汇总 +- 公共指标在这里统一计算,ADS 层只引用不重算 +- 核心字段和扩展字段分离。核心字段(20-30 个)稳定不变 + +**汇总粒度的选择:** +- 用户粒度 + 近 1 天/7 天/30 天是最常见的模式 +- 按天 + 维度聚合是默认选择 +- 按月/季度聚合在 ADS 层做,不在 DWS 层 + +## DIM — 维度层 + +**职责:** 统一维度管理。 + +**关键决策:** 每个维度属性选哪种 SCD 策略。 +- 用户等级 → SCD2(需要历史追溯) +- 用户手机号 → SCD2(合规要求) +- 纠错类更新 → SCD1(直接覆盖) +- 商品分类 → 看业务需求。分类经常调整的话用 SCD2 + +**维度数据量 < 500 万:** 全量快照(每天一份)比拉链表好维护。 +**维度数据量 > 500 万:** SCD2 拉链表。 + +## ADS — 应用层 + +**职责:** 面向具体消费场景。 + +**可以做的:** +- 从 DWS/DIM 读取数据 +- 按报表/API/大屏需求做最终聚合 +- 写入 MySQL、Redis、ES 等外部系统 + +**不能做的:** +- 引入新的计算逻辑(应该下沉到 DWS) +- 直接读 ODS +- 做跨主题域的复杂关联(应该在 DWS 层完成) + +## 五家企业架构对比 + +| | 阿里 | 美团 | 网易严选 | 马蜂窝 | 恒丰银行 | +|---|------|------|---------|--------|---------| +| 引擎 | MaxCompute | Flink+Kafka | Spark+ClickHouse | Hive+Presto | Hive+GaussDB | +| 特色 | OneData 平台,指标体系 | 实时数仓,Flink SQL | 实时 OLAP | 轻量灵活 | 数据治理优先 | +| ODS | Binlog+日志原样入湖 | Kafka Topic | 同阿里 | 同阿里 | 同阿里 | +| DWD | 维度退化宽表 | Flink 清洗流 | 清洗+标准化 | 清洗+标准化 | 清洗+脱敏 | +| DWS | 公共指标汇总 | 多维聚合 | 多维聚合 | 多维聚合 | 多维聚合+审计 | +| ADS | DataWorks 出表 | ClickHouse | ClickHouse | MySQL/Redis | GaussDB | + +共同规律:不管用哪个引擎,分层逻辑是一样的。 + +## 分层数据流 + +``` +✅ 允许: + ODS → DWD → DWS → ADS + ODS → DIM → (被 DWD/DWS/ADS 引用) + DWD → DWM → DWS(可选中间层) + ADS → DWS/DIM(标准路径) + +⚠ 允许但需标注原因: + ADS → DWD(敏捷看板需要明细数据,必须在 ADS 层注释说明) + +❌ 禁止: + ADS → ODS + DWS → DWD(反向依赖) + DWM → ODS(跨多层) +``` diff --git a/skills/data-warehouse-modeling/references/methodology-comparison.md b/skills/data-warehouse-modeling/references/methodology-comparison.md new file mode 100644 index 0000000..c128f8d --- /dev/null +++ b/skills/data-warehouse-modeling/references/methodology-comparison.md @@ -0,0 +1,144 @@ +# 建模方法论对比 + +## 选方法论的现实逻辑 + +别从"哪个方法论最好"出发。从你的约束条件出发: + +- 团队多大?5 人以下别碰 Data Vault,维护成本你扛不住 +- 有没有合规要求?金融医疗选 Data Vault,没有就 Kimball +- 数据源乱不乱?10 个以上异构数据源,考虑 Data Vault 或 Inmon 做整合层 +- 公司文化能不能推标准?能推就 OneData,推不动就先 Kimball 出活 + +## Kimball 维度建模 + +Ralph Kimball 在 1996 年提出的方法论。至今仍然是大多数公司的实际选择——不是因为最好,是因为最快能出东西。 + +### 四步法的真正含义 + +1. **选业务过程** — 不是选"想分析什么",是选"业务上发生了什么原子事件"。下单是一个过程,支付是另一个过程。别把它们混在一起。 +2. **声明粒度** — 最容易被跳过、也最致命的步骤。"订单粒度"太模糊——是每笔订单一行,还是每笔订单的每个商品一行?粒度没定清楚,后面所有度量都是错的。 +3. **识别维度** — 谁、何时、何地、什么。维度是分析的角度。 +4. **识别事实** — 可加的度量(金额、数量)。半可加的(余额)和不可加的(比率)要特殊处理。 + +### 星型模型为什么是默认选择 + +星型模型(一张事实表 + 多张维度表)不是唯一选择,但在以下条件同时满足时是最务实的选择: +- 下游是 BI 工具或直接 SQL 查询 +- 查询模式以"按维度切片聚合"为主 +- 团队对 JOIN 的理解不需要培训 + +雪花模型在理论上有存储优势,实际中增加的 JOIN 复杂度远超省下的空间。星座模型(多事实表共享维度)是规模增长后的自然演化,不用刻意追求。 + +### SCD 怎么选 + +- **SCD2(拉链表)**:需要历史追溯的场景。用户等级变了,历史订单的分析不能跟着变。实现成本高,但这是正确答案。 +- **SCD1(覆盖)**:不需要历史的场景。纠错型更新、临时字段。 +- **SCD3(加列)**:教科书里常见,实际项目中很少用。只保留"上一个值"的场景太窄了。 +- **SCD4(快照表)**:每天一份全量快照。适合维度数据量不大的场景(几百万以内),比拉链表好维护。 + +### 一致性维度 + +这是 Kimball 方法论中最被低估的概念。两个数据集市要能交叉分析,必须共享同一套维度定义。`dim_user` 在交易域和流量域必须是同一张表——不是"结构相似的两张表",是同一张。 + +实际操作:先统一核心维度(用户、商品、时间、地域),业务维度允许各域自行管理,逐步收敛。 + +## Data Vault 2.0 + +Dan Linstedt 设计的方法论,核心思想是**把结构、关系、描述性信息分开存储**。 + +### 三种表 + +**Hub(中心表)**:存业务键。一个 Hub = 一个核心实体(客户、商品、订单)。 +- 只存 Hash Key + Business Key + Load Date + Source +- **Insert-only**:业务键一旦录入,永不修改 + +**Link(链接表)**:存 Hub 之间的关系。 +- 也是 Insert-only +- 关系变了就加新行,保留完整关系历史 + +**Satellite(卫星表)**:存描述性属性及其变更历史。 +- 用 HashDiff 检测变更:计算所有描述列的哈希,变了就插新行 +- 一个 Hub 可以有多个 Satellite(基本信息一个、地址信息一个、分类信息一个) +- Satellite 之间不能直接关联,必须通过 Hub 或 Link + +### 什么时候用 Data Vault + +**适合的场景:** +- 多个异构数据源需要整合(5 个以上不同系统) +- 金融、医疗、政府——监管要求数据变更必须可追溯 +- 团队并行开发,不同团队负责不同业务域 + +**不适合的场景:** +- 数据源单一、结构稳定 +- 团队小(5 人以下维护成本太高) +- 没有审计合规需求 + +### Data Vault 和 Kimball 的关系 + +Data Vault 不是 Kimball 的替代品,是互补的: + +``` +源系统 → Raw Vault(数据保管)→ Business Vault(业务规则)→ Info Marts(Kimball 星型) +``` + +用 Data Vault 做整合层(保证可追溯、可并行加载),用 Kimball 星型做报表层(保证查询性能和易用性)。这是企业级数仓的最佳实践。 + +## Inmon 范式建模 + +Bill Inmon 的方法:先建企业级 3NF(第三范式)模型作为"单一数据源",再在上面建维度化的数据集市。 + +**问题:** 建设周期太长。3NF 模型动辄 6-12 个月才能出第一个可用集市,大多数公司扛不住这个周期。 + +**实际使用:** 很少有公司从头到尾走 Inmon。但 Inmon 的"企业级数据模型"思想值得借鉴——在 Kimball 建模之前,先画一遍企业级概念模型,理清实体关系,再按业务优先级逐步建设。 + +## OneData(阿里) + +阿里 OneData 体系由三部分组成,核心价值不在技术框架,在管理流程。 + +### OneModel — 统一建模 + +指标标准化的流程。核心公式: + +``` +派生指标 = 原子指标 + 业务限定 + 统计周期 + 统计粒度 +``` + +关键不在公式本身,在于**原子指标的定义权必须收归数据团队**。业务方说"GMV"时,数据团队必须追问:含不含退款?含不含优惠?统计截止时间点是什么?把这些答案写进指标定义文档,然后全公司只有这一个定义。 + +### OneID — 实体统一 + +通过 ID-Mapping 把不同系统中的同一实体(用户/设备)合并为统一标识。技术上有图计算、规则引擎等方案,但真正的难点在数据质量——脏数据太多的话,ID-Mapping 的准确率会很低。 + +### OneService — 数据服务 + +把数据查询能力封装为统一 API。从烟囱式的"一个需求一个接口"演进到元数据驱动的模板化服务。 + +## Medallion(Databricks) + +Databricks 推出的湖仓一体分层架构: + +- **Bronze**:原始数据,只追加,保留所有字段(包括错误的) +- **Silver**:清洗、去重、标准化。相当于 DWD 层 +- **Gold**:面向业务的聚合表。相当于 DWS + ADS 层 + +**适用条件:** 你的存储基础是 Delta Lake / Iceberg / Hudi 这类开放格式。如果不是,Medallion 和传统分层的区别只是叫法不同。 + +## 混合架构 + +企业级数仓的务实做法是混合架构: + +``` +源系统 + ↓ +ODS / Bronze(原样保留) + ↓ +DWD / Silver(清洗标准化) + ↓ +Data Vault(可选,审计需要的场景才加) + ↓ +DWS / Gold(Kimball 星型,面向报表) + ↓ +ADS / Presentation(应用层,安全面视图) +``` + +不需要 Data Vault 的场景,DWD 直接连 DWS。不要为了"架构完整性"加不需要的层。 diff --git a/skills/data-warehouse-modeling/references/naming-conventions.md b/skills/data-warehouse-modeling/references/naming-conventions.md new file mode 100644 index 0000000..73e645a --- /dev/null +++ b/skills/data-warehouse-modeling/references/naming-conventions.md @@ -0,0 +1,141 @@ +# 命名规范与词根词典 + +## 为什么命名规范重要 + +不是为了好看。是为了让新入职的人三天内能看懂表结构,不需要追着原作者问"这个字段什么意思"。 + +命名规范的核心原则:见名知义。做不到这点的命名就是错的。 + +## 表命名 + +### 通用规则 + +- 全小写,下划线分隔 +- 禁止中文字符、拼音、特殊符号 +- 表名最长 64 字符 +- 用英文词根,不用缩写(除非是行业公认的,如 gmv、sku) + +### 各层模板 + +| 层 | 格式 | 示例 | +|----|------|------| +| ODS | `ods_{源系统}_{源表名}_{增量标记}` | `ods_mysql_trade_order_di` | +| DWD | `dwd_{主题域}_{业务过程}_{粒度}_{增量标记}` | `dwd_trade_order_detail_di` | +| DWS | `dws_{主题域}_{汇总粒度}_{业务描述}_{增量标记}` | `dws_trade_user_1d_order_df` | +| DIM | `dim_{主题域}_{维度描述}` | `dim_user_profile` | +| ADS | `ads_{业务场景}_{报表描述}` | `ads_sales_daily_report` | +| dbt staging | `stg_{source}__{entity}` | `stg_ecommerce__orders` | +| dbt intermediate | `int_{description}` | `int_orders_enriched` | +| dbt marts | `fct_{process}` / `dim_{entity}` | `fct_orders` / `dim_users` | + +### 增量标记 + +| 标记 | 含义 | +|------|------| +| `di` | 日增量 | +| `df` | 日全量 | +| `mi` | 分钟增量 | +| `hi` | 小时增量 | +| `ri` | 实时增量 | + +## 字段命名 + +### 类型后缀 + +| 后缀 | 含义 | 类型 | 示例 | +|------|------|------|------| +| `_id` | 业务键 | BIGINT/STRING | `user_id`, `order_id` | +| `_sk` | 代理键(数仓内部) | BIGINT | `user_sk` | +| `_amt` | 金额 | DECIMAL(18,2) | `pay_amt`, `refund_amt` | +| `_cnt` | 计数 | BIGINT | `order_cnt`, `click_cnt` | +| `_rate` | 比率 | DECIMAL(10,6) | `conv_rate` | +| `_pct` | 百分比 | DECIMAL(5,2) | `discount_pct` | +| `_name` | 名称 | VARCHAR | `category_name` | +| `_code` | 编码 | STRING | `province_code` | +| `_time` / `_at` | 时间戳 | TIMESTAMP | `create_time`, `paid_at` | +| `_date` | 日期 | DATE/STRING | `order_date` | +| `_type` | 类型 | STRING/INT | `pay_type` | + +### 布尔前缀 + +- `is_` 前缀:`is_new_user`, `is_paid` +- 类型:BOOLEAN 或 TINYINT (0/1) + +### 时间字段约定 + +| 字段名 | 含义 | +|--------|------| +| `create_time` | 创建时间 | +| `update_time` | 更新时间 | +| `ds` 或 `dt` | 分区字段(`yyyy-MM-dd`) | +| `start_dt` / `end_dt` | SCD2 生效/失效日期 | +| `is_current` | SCD2 当前行标记 | +| `etl_time` | ETL 处理时间 | + +## 数据类型 + +| 语义 | 类型 | 说明 | +|------|------|------| +| 主键/外键 | BIGINT | 统一 BIGINT,别用 STRING 做键 | +| 金额 | DECIMAL(18,2) | 精度 18,小数 2 位 | +| 比率 | DECIMAL(10,6) | 高精度 | +| 计数 | BIGINT | 避免溢出 | +| 布尔 | TINYINT | 0/1 | +| 日期 | DATE 或 STRING | STRING 用 `yyyy-MM-dd` | + +## 词根词典 + +### 度量词根 + +| 词根 | 含义 | 示例 | +|------|------|------| +| `cnt` | 计数 | `order_cnt` | +| `amt` | 金额 | `pay_amt` | +| `qty` | 数量(件/个) | `item_qty` | +| `rate` | 比率/转化率 | `conv_rate` | +| `avg` | 平均值 | `avg_order_amt` | +| `uv` | 去重用户数 | `page_uv` | +| `pv` | 浏览次数 | `item_pv` | +| `gmv` | 成交总额 | `gmv_amt` | +| `aov` | 客单价 | `aov_amt` | + +### 时间词根 + +| 词根 | 含义 | 示例 | +|------|------|------| +| `td` | 当天 | `td_order_cnt` | +| `yd` | 昨天 | `yd_pay_amt` | +| `1d`/`7d`/`30d` | 近 N 天 | `last7d_order_cnt` | +| `wk` | 周 | `wk_new_user_cnt` | +| `mth` | 月 | `mth_gmv` | +| `ytd` | 年初至今 | `ytd_revenue` | + +### 实体词根 + +`user`, `member`, `item`, `product`, `sku`, `spu`, `order`, `cart`, `payment`, `refund`, `shop`, `store`, `brand`, `category`, `channel`, `coupon`, `session`, `event`, `page` + +### 技术词根 + +| 词根 | 含义 | +|------|------| +| `id` | 业务键 | +| `sk` | 代理键 | +| `hk` | Hash Key(Data Vault) | +| `dt` | 日期分区 | +| `ts` | 时间戳 | +| `source` | 数据来源系统 | +| `ver` | 版本号 | + +### 地理词根 + +`country`, `province`, `state`, `city`, `district`, `region`, `lng`(经度), `lat`(纬度) + +## 词根扩展规则 + +新增词根的前提: +1. 在团队中被 3 个以上表/指标使用 +2. 不与现有词根冲突 +3. 通过建模评审 +4. 更新词典并通知全团队 + +别为了"完整性"预创建一堆词根。用到再建。 diff --git a/skills/data-warehouse-modeling/references/realtime-dw-design.md b/skills/data-warehouse-modeling/references/realtime-dw-design.md new file mode 100644 index 0000000..840b6d4 --- /dev/null +++ b/skills/data-warehouse-modeling/references/realtime-dw-design.md @@ -0,0 +1,120 @@ +# 实时数仓 + +## Lambda vs Kappa + +**Lambda(批+流双跑):** 离线批处理保准确性,流处理保实时性,两套结果合并。 +- 优点:批处理结果可靠,流处理结果及时 +- 缺点:两套代码要维护,合并逻辑容易出错 + +**Kappa(纯流):** 统一用流处理,Kafka 当数据总线,需要重算就重放消息。 +- 优点:一套代码,架构简单 +- 缺点:流处理的端到端一致性比批处理难保障 + +**现实选择:** 大多数公司从 Lambda 开始(离线数仓已经有了,加个流处理层),逐步往 Kappa 迁移。完全 Kappa 的前提是流处理基础设施足够成熟。 + +## 国内主流架构 + +``` +MySQL/Binlog ──Flink CDC──→ Kafka ──Flink──→ Kafka ──Flink──→ OLAP + │ (ODS) (DWD) (DWS) (ADS) + │ │ +日志/Flume ──────────────→ Kafka │ + (ODS) │ + ▼ + HBase/Redis(DIM) +``` + +分层逻辑和离线一样——ODS → DWD → DWS → ADS,只是存储从 Hive 换成了 Kafka,计算从 Spark 换成了 Flink。 + +## 各层存储选择 + +| 层 | 存储 | 原因 | +|----|------|------| +| ODS | Kafka Topic | 支持多消费、可重放 | +| DWD | Kafka Topic | 同上 | +| DWS | Kafka + OLAP 双写 | Kafka 给下游流消费,OLAP 给查询 | +| DIM | HBase / Redis | 维度表需要点查,不支持全表扫描 | +| ADS | ClickHouse / Doris / MySQL | 面向最终查询 | + +## OLAP 引擎选择 + +别被参数对比表迷惑,实际选择看你的场景: + +| 你的场景 | 选什么 | 原因 | +|---------|--------|------| +| 日志分析、大宽表单表查询 | ClickHouse | 列存扫描极快,但 JOIN 弱 | +| 需要 MySQL 协议、实时更新 | Doris | 兼容 MySQL,支持 Upsert | +| 多表 JOIN、复杂查询 | StarRocks | 向量化 + MPP | +| 超高并发点查(毫秒级) | Pinot | 索引丰富,但运维重 | + +## 实时建模的三个难点 + +### 1. 维度关联 + +离线里 JOIN 维度表很自然,流处理里维度在变化,怎么关联到"当时的值"? + +- **小维度表(< 100MB)**:广播到每个 Flink 节点(Broadcast State) +- **中等维度表**:异步 IO 查 HBase/Redis,加本地缓存 +- **大维度表或需要"当时值"**:CDC 维度变更流,按时间区间 JOIN + +### 2. 乱序数据 + +事件时间和处理时间不一样。用户 12:00:00 的点击,到服务器可能 12:00:05。 + +- 用 Watermark 声明允许的乱序窗口 +- 大多数业务 5 秒够了,金融场景可能要 30 秒 +- Watermark 太大 → 延迟高;太小 → 数据丢 + +### 3. 幂等性 + +流处理可能重复消费,指标多算。 + +- ClickHouse:`ReplacingMergeTree` 或 `CollapsingMergeTree` +- Doris:Unique Key 模型 +- Kafka:事务消费 + Flink Checkpoint(Exactly-Once) + +## Flink SQL 模板 + +### ODS → DWD(清洗 + 维度退化) + +```sql +INSERT INTO dwd_trade_order_detail +SELECT + order_id, + user_id, + product_id, + dim.category_name, + dim.brand_name, + CAST(order_amt AS DECIMAL(18, 2)) AS order_amt, + order_time, + CURRENT_TIMESTAMP AS etl_time +FROM ods_trade_order AS o +LEFT JOIN dim_product FOR SYSTEM_TIME AS OF o.proctime AS dim + ON o.product_id = dim.product_id +WHERE order_id IS NOT NULL AND order_amt > 0; +``` + +### DWD → DWS(分钟级窗口聚合) + +```sql +INSERT INTO dws_trade_user_1min +SELECT + user_id, + TUMBLE_START(order_time, INTERVAL '1' MINUTE) AS window_start, + TUMBLE_END(order_time, INTERVAL '1' MINUTE) AS window_end, + COUNT(DISTINCT order_id) AS order_cnt, + SUM(pay_amt) AS total_pay_amt +FROM dwd_trade_order_detail +GROUP BY user_id, TUMBLE(order_time, INTERVAL '1' MINUTE); +``` + +## 延迟选择 + +| 需要的延迟 | 方案 | 成本 | +|-----------|------|------| +| 毫秒 | 流处理直出 + OLAP 缓存 | 高 | +| 秒级 | Flink → ClickHouse/Doris | 中 | +| 分钟 | Flink 微批 / Spark Streaming | 中低 | +| 小时 | 离线小时任务 | 低 | + +别追求"越实时越好"。先问清楚业务方需要什么延迟,再选方案。大多数报表 T+1 就够了,强行实时只会增加运维负担。 diff --git a/skills/data-warehouse-modeling/references/subject-domains.md b/skills/data-warehouse-modeling/references/subject-domains.md new file mode 100644 index 0000000..5d48e5d --- /dev/null +++ b/skills/data-warehouse-modeling/references/subject-domains.md @@ -0,0 +1,35 @@ +# 主题域划分 + +## 怎么划 + +主题域的划分没有唯一正确答案,但有一个原则:**按业务过程划分,不要按部门或系统划分。** + +按部门划分的问题:组织架构会变,变了就要重构。 +按系统划分的问题:一个业务过程可能跨多个系统(下单在 App,支付在支付网关)。 +按业务过程划分:稳定、与业务逻辑对齐、和 Kimball 总线矩阵方法论一致。 + +## 12 个常见主题域 + +| 主题域 | 核心业务过程 | 核心实体 | +|--------|------------|---------| +| 交易 | 下单、支付、发货、收货、退款 | 订单、支付单、退款单 | +| 用户 | 注册、登录、活跃、流失、召回 | 用户、设备、账户 | +| 流量 | 页面浏览、点击、停留、搜索 | 页面、事件、会话 | +| 商品 | 发布、上架、下架、审核 | 商品、SKU、SPU、类目 | +| 营销 | 领券、用券、活动参与 | 优惠券、活动、红包 | +| 物流 | 揽收、运输、派送、签收 | 包裹、运单、仓库 | +| 供应链 | 采购、入库、出库、盘点 | 仓库、供应商、库存 | +| 财务 | 结算、对账、开票、收入确认 | 账单、发票、结算单 | +| 风控 | 审核预警、反欺诈 | 风险事件、规则 | +| 内容 | 发布、审核、推荐、互动 | 文章、视频、评论 | +| 搜索推荐 | 搜索、曝光、点击、转化 | 搜索词、推荐位 | +| 客服 | 咨询、投诉、工单、评价 | 工单、评价 | + +## 实际操作步骤 + +1. **别一上来就画 12 个域** — 先建核心域(交易、用户、流量),其他域按业务优先级逐步加 +2. **先画总线矩阵再建表** — 业务过程 × 可用维度,填完了再动手写 DDL +3. **每个域指定一个 owner** — 没有 owner 的域,表结构三天就乱 +4. **命名规范先于建表** — 先定好 `trade` 还是 `order` 做交易域的缩写,别混着用 + +→ `references/bus-matrix.md` 有总线矩阵模板和维度 DDL diff --git a/skills/data-warehouse-modeling/scripts/sql-templates.md b/skills/data-warehouse-modeling/scripts/sql-templates.md new file mode 100644 index 0000000..463b6b6 --- /dev/null +++ b/skills/data-warehouse-modeling/scripts/sql-templates.md @@ -0,0 +1,343 @@ +# 数仓 SQL 建模模板 + +以下模板基于 Hive/Spark SQL 语法,适用于 MaxCompute、Hive、Spark 等离线引擎。 + +## 一、ODS 建表模板 + +```sql +-- ODS 贴源层建表 +-- 特点:原样保留源数据,按天分区,定义生命周期 +CREATE TABLE IF NOT EXISTS ods_mysql_trade_order_di ( + id BIGINT COMMENT '主键ID', + order_id STRING COMMENT '订单编号', + user_id BIGINT COMMENT '用户ID', + product_id BIGINT COMMENT '商品ID', + sku_id BIGINT COMMENT 'SKU ID', + order_amt DECIMAL(18,2) COMMENT '订单金额', + discount_amt DECIMAL(18,2) COMMENT '优惠金额', + pay_amt DECIMAL(18,2) COMMENT '实付金额', + pay_type STRING COMMENT '支付方式', + order_status STRING COMMENT '订单状态', + province_code STRING COMMENT '省份编码', + channel_code STRING COMMENT '渠道编码', + create_time TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP COMMENT '更新时间', + etl_time TIMESTAMP COMMENT 'ETL处理时间' +) +COMMENT '交易域-订单表-日增量' +PARTITIONED BY (ds STRING COMMENT '业务日期 yyyy-MM-dd') +STORED AS ORC +TBLPROPERTIES ( + 'orc.compress' = 'SNAPPY', + 'lifecycle' = '180' -- 保留180天 +); +``` + +## 二、DWD 明细宽表模板 + +```sql +-- DWD 明细层建表 +-- 特点:清洗后标准化数据,维度退化,保持最细粒度 +CREATE TABLE IF NOT EXISTS dwd_trade_order_detail_di ( + order_detail_id STRING COMMENT '订单明细唯一标识', + order_id STRING COMMENT '订单编号', + user_id BIGINT COMMENT '用户ID', + -- 维度退化字段 + user_level STRING COMMENT '用户等级(退化维度)', + product_id BIGINT COMMENT '商品ID', + product_name STRING COMMENT '商品名称(退化维度)', + category1_name STRING COMMENT '一级类目(退化维度)', + category2_name STRING COMMENT '二级类目(退化维度)', + brand_name STRING COMMENT '品牌名称(退化维度)', + sku_id BIGINT COMMENT 'SKU ID', + -- 度量字段 + order_cnt BIGINT COMMENT '购买数量', + original_amt DECIMAL(18,2) COMMENT '原始金额', + discount_amt DECIMAL(18,2) COMMENT '优惠金额', + pay_amt DECIMAL(18,2) COMMENT '实付金额', + -- 维度字段 + province_code STRING COMMENT '省份编码', + city_code STRING COMMENT '城市编码', + channel_code STRING COMMENT '渠道编码', + pay_type STRING COMMENT '支付方式', + order_status STRING COMMENT '订单状态', + is_new_user TINYINT COMMENT '是否新用户 0否1是', + -- 时间字段 + order_time TIMESTAMP COMMENT '下单时间', + pay_time TIMESTAMP COMMENT '支付时间', + etl_time TIMESTAMP COMMENT 'ETL处理时间' +) +COMMENT '交易域-订单明细-日增量' +PARTITIONED BY (ds STRING COMMENT '业务日期 yyyy-MM-dd') +STORED AS ORC +TBLPROPERTIES ( + 'orc.compress' = 'SNAPPY', + 'lifecycle' = '730' -- 保留2年 +); +``` + +### DWD ETL 模板(ODS → DWD) + +```sql +-- DWD ETL: 清洗 + 维度退化 +INSERT OVERWRITE TABLE dwd_trade_detail_di PARTITION (ds = '${bizdate}') +SELECT + -- 生成明细唯一标识 + CONCAT(o.order_id, '_', o.sku_id) AS order_detail_id, + o.order_id, + o.user_id, + dim_u.user_level, + o.product_id, + dim_p.product_name, + dim_p.category1_name, + dim_p.category2_name, + dim_p.brand_name, + o.sku_id, + o.order_cnt, + o.order_amt AS original_amt, + o.discount_amt, + o.pay_amt, + o.province_code, + o.city_code, + o.channel_code, + o.pay_type, + o.order_status, + CASE WHEN dim_u.create_time >= DATE_SUB('${bizdate}', 30) THEN 1 ELSE 0 END AS is_new_user, + o.order_time, + o.pay_time, + CURRENT_TIMESTAMP() AS etl_time +FROM ods_mysql_trade_order_di o +LEFT JOIN dim_user_info_df dim_u ON o.user_id = dim_u.user_id AND dim_u.ds = '${bizdate}' +LEFT JOIN dim_product_info_df dim_p ON o.product_id = dim_p.product_id AND dim_p.ds = '${bizdate}' +WHERE o.ds = '${bizdate}' + AND o.order_id IS NOT NULL + AND o.order_status IN ('PAID', 'SHIPPED', 'COMPLETED', 'REFUNDING') +; +``` + +## 三、DIM 维度表模板 + +### 全量快照维度表 + +```sql +-- DIM 维度表:全量快照(适合维度数据量不大的场景) +CREATE TABLE IF NOT EXISTS dim_user_info_df ( + user_id BIGINT COMMENT '用户ID', + user_name STRING COMMENT '用户名', + phone STRING COMMENT '手机号(脱敏)', + gender STRING COMMENT '性别', + age_group STRING COMMENT '年龄段', + user_level STRING COMMENT '用户等级', + province_code STRING COMMENT '省份编码', + city_code STRING COMMENT '城市编码', + register_channel STRING COMMENT '注册渠道', + is_active TINYINT COMMENT '是否活跃 0否1是', + create_time TIMESTAMP COMMENT '注册时间', + update_time TIMESTAMP COMMENT '信息更新时间', + etl_time TIMESTAMP COMMENT 'ETL处理时间' +) +COMMENT '用户域-用户维度表-日全量' +PARTITIONED BY (ds STRING COMMENT '快照日期 yyyy-MM-dd') +STORED AS ORC +TBLPROPERTIES ('lifecycle' = '365'); +``` + +### SCD Type 2 拉链表 + +```sql +-- DIM 维度表:拉链表(SCD Type 2,保留历史变更) +CREATE TABLE IF NOT EXISTS dim_product_info_zipper ( + product_id BIGINT COMMENT '商品ID(业务键)', + product_sk BIGINT COMMENT '代理键', + product_name STRING COMMENT '商品名称', + category1_name STRING COMMENT '一级类目', + category2_name STRING COMMENT '二级类目', + brand_name STRING COMMENT '品牌名称', + price DECIMAL(18,2) COMMENT '价格', + status STRING COMMENT '状态', + start_dt STRING COMMENT '生效日期', + end_dt STRING COMMENT '失效日期(9999-12-31表示当前有效)', + is_current TINYINT COMMENT '是否当前版本 0否1是', + etl_time TIMESTAMP COMMENT 'ETL处理时间' +) +COMMENT '商品域-商品维度拉链表' +STORED AS ORC +TBLPROPERTIES ('lifecycle' = '9999'); +``` + +### 拉链表更新 DML + +```sql +-- Step 1: 关闭旧记录 +INSERT OVERWRITE TABLE dim_product_info_zipper +SELECT + product_id, product_sk, product_name, + category1_name, category2_name, brand_name, price, + status, start_dt, + CASE WHEN product_id IN (SELECT product_id FROM ods_product WHERE ds = '${bizdate}') + THEN '${bizdate}' ELSE end_dt END AS end_dt, + CASE WHEN product_id IN (SELECT product_id FROM ods_product WHERE ds = '${bizdate}') + THEN 0 ELSE is_current END AS is_current, + etl_time +FROM dim_product_info_zipper + +UNION ALL + +-- Step 2: 插入新记录 +SELECT + p.product_id, + ROW_NUMBER() OVER (ORDER BY p.product_id) + 999999999 AS product_sk, + p.product_name, p.category1_name, p.category2_name, + p.brand_name, p.price, p.status, + '${bizdate}' AS start_dt, + '9999-12-31' AS end_dt, + 1 AS is_current, + CURRENT_TIMESTAMP() AS etl_time +FROM ods_product p +WHERE p.ds = '${bizdate}' +; +``` + +## 四、DWS 汇总表模板 + +```sql +-- DWS 汇总层:用户粒度 + 近1天交易汇总 +CREATE TABLE IF NOT EXISTS dws_trade_user_1d_df ( + user_id BIGINT COMMENT '用户ID', + order_cnt BIGINT COMMENT '下单次数', + order_product_cnt BIGINT COMMENT '下单商品件数', + order_amt DECIMAL(18,2) COMMENT '下单金额', + pay_cnt BIGINT COMMENT '支付次数', + pay_amt DECIMAL(18,2) COMMENT '支付金额', + refund_cnt BIGINT COMMENT '退款次数', + refund_amt DECIMAL(18,2) COMMENT '退款金额', + favor_cnt BIGINT COMMENT '收藏次数', + cart_cnt BIGINT COMMENT '加购次数', + coupon_used_cnt BIGINT COMMENT '优惠券使用次数', + coupon_discount_amt DECIMAL(18,2) COMMENT '优惠券抵扣金额', + etl_time TIMESTAMP COMMENT 'ETL处理时间' +) +COMMENT '交易域-用户粒度-近1天汇总-日全量' +PARTITIONED BY (ds STRING COMMENT '统计日期 yyyy-MM-dd') +STORED AS ORC; +``` + +### DWS ETL 模板 + +```sql +-- DWS ETL: 多表聚合 → 用户粒度汇总 +INSERT OVERWRITE TABLE dws_trade_user_1d_df PARTITION (ds = '${bizdate}') +SELECT + t.user_id, + -- 订单指标 + COALESCE(t.order_cnt, 0) AS order_cnt, + COALESCE(t.order_product_cnt, 0) AS order_product_cnt, + COALESCE(t.order_amt, 0) AS order_amt, + -- 支付指标 + COALESCE(p.pay_cnt, 0) AS pay_cnt, + COALESCE(p.pay_amt, 0) AS pay_amt, + -- 退款指标 + COALESCE(r.refund_cnt, 0) AS refund_cnt, + COALESCE(r.refund_amt, 0) AS refund_amt, + -- 互动指标 + COALESCE(f.favor_cnt, 0) AS favor_cnt, + COALESCE(c.cart_cnt, 0) AS cart_cnt, + -- 营销指标 + COALESCE(cpn.coupon_used_cnt, 0) AS coupon_used_cnt, + COALESCE(cpn.coupon_discount_amt, 0) AS coupon_discount_amt, + CURRENT_TIMESTAMP() AS etl_time +FROM ( + SELECT user_id, + COUNT(DISTINCT order_id) AS order_cnt, + SUM(order_cnt) AS order_product_cnt, + SUM(pay_amt) AS order_amt + FROM dwd_trade_order_detail_di + WHERE ds = '${bizdate}' + GROUP BY user_id +) t +LEFT JOIN (...) p ON t.user_id = p.user_id +LEFT JOIN (...) r ON t.user_id = r.user_id +LEFT JOIN (...) f ON t.user_id = f.user_id +LEFT JOIN (...) c ON t.user_id = c.user_id +LEFT JOIN (...) cpn ON t.user_id = cpn.user_id +; +``` + +## 五、ADS 应用表模板 + +```sql +-- ADS 应用层:销售日报 +CREATE TABLE IF NOT EXISTS ads_sales_daily_report ( + stat_date STRING COMMENT '统计日期', + province_code STRING COMMENT '省份编码', + province_name STRING COMMENT '省份名称', + category1_name STRING COMMENT '一级类目', + gmv_amt DECIMAL(18,2) COMMENT 'GMV', + pay_amt DECIMAL(18,2) COMMENT '支付金额', + refund_amt DECIMAL(18,2) COMMENT '退款金额', + order_cnt BIGINT COMMENT '订单数', + pay_user_cnt BIGINT COMMENT '支付用户数', + aov_amt DECIMAL(18,2) COMMENT '客单价', + refund_rate DECIMAL(10,6) COMMENT '退款率', + pay_conv_rate DECIMAL(10,6) COMMENT '支付转化率', + etl_time TIMESTAMP COMMENT 'ETL处理时间' +) +COMMENT '销售日报-省份类目粒度' +STORED AS ORC; + +-- ADS ETL +INSERT OVERWRITE TABLE ads_sales_daily_report +SELECT + '${bizdate}' AS stat_date, + province_code, + province_name, + category1_name, + SUM(pay_amt + refund_amt) AS gmv_amt, + SUM(pay_amt) AS pay_amt, + SUM(refund_amt) AS refund_amt, + COUNT(DISTINCT order_id) AS order_cnt, + COUNT(DISTINCT user_id) AS pay_user_cnt, + CASE WHEN COUNT(DISTINCT user_id) > 0 + THEN SUM(pay_amt) / COUNT(DISTINCT user_id) + ELSE 0 END AS aov_amt, + CASE WHEN SUM(pay_amt) > 0 + THEN SUM(refund_amt) / SUM(pay_amt) + ELSE 0 END AS refund_rate, + CASE WHEN SUM(order_amt) > 0 + THEN SUM(pay_amt) / SUM(order_amt) + ELSE 0 END AS pay_conv_rate, + CURRENT_TIMESTAMP() AS etl_time +FROM dwd_trade_order_detail_di +WHERE ds = '${bizdate}' +GROUP BY province_code, province_name, category1_name +; +``` + +## 六、通用 DML 模式 + +### 增量插入(INSERT OVERWRITE 分区) + +```sql +-- 每日增量写入指定分区 +INSERT OVERWRITE TABLE {table_name} PARTITION (ds = '${bizdate}') +SELECT ... FROM ... WHERE ds = '${bizdate}'; +``` + +### 全量覆盖(TRUNCATE + INSERT) + +```sql +-- 全量维度表:覆盖写入 +INSERT OVERWRITE TABLE {table_name} PARTITION (ds = '${bizdate}') +SELECT ... FROM ...; +``` + +### MERGE(Upsert,支持 MERGE 的引擎) + +```sql +MERGE INTO {target_table} t +USING {source_table} s +ON t.{primary_key} = s.{primary_key} AND t.ds = '${bizdate}' +WHEN MATCHED THEN UPDATE SET ... +WHEN NOT MATCHED THEN INSERT ... +; +``` From d8e6a2f5f7ac685202a8e28e6f54b26c9c2cdbea Mon Sep 17 00:00:00 2001 From: jnMetaCode Date: Tue, 9 Jun 2026 16:02:05 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E6=95=B0=E4=BB=93=E5=BB=BA?= =?UTF-8?q?=E6=A8=A1=20skill=20v5=20=E2=80=94=20=E5=BC=95=E5=85=A5?= =?UTF-8?q?=E5=88=B6=E9=80=A0=E4=B8=9A/=E5=8C=BB=E7=96=97/=E6=94=BF?= =?UTF-8?q?=E5=8A=A1/=E8=83=BD=E6=BA=90/=E5=86=9C=E4=B8=9A=E8=A1=8C?= =?UTF-8?q?=E4=B8=9A=E7=BB=8F=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化内容: - 方法论选择改为「行业特性 → 团队规模 → 方法论」三层决策 - 新增 industry-patterns.md(6 个行业建模模式、BOM 桥接表、 FHIR 映射、三库架构、时序数据降采样、物候维度、OT/IT 融合) - 反模式新增传统行业特有陷阱(源数据未 Profile、 递归 CTE 处理层级、时序直入 Hive、合规后置) - 新增传统行业数据格式处理(时序/GIS/图/非结构化/层级递归) - SQL 模板表名补全库名前缀(ods./dwd./dws./dim./ads.) - 去 AI 味,实战语气 --- skills/data-warehouse-modeling/SKILL.md | 167 ++++++------ .../references/antipatterns.md | 115 ++++----- .../references/industry-patterns.md | 241 ++++++++++++++++++ .../scripts/sql-templates.md | 28 +- 4 files changed, 385 insertions(+), 166 deletions(-) create mode 100644 skills/data-warehouse-modeling/references/industry-patterns.md diff --git a/skills/data-warehouse-modeling/SKILL.md b/skills/data-warehouse-modeling/SKILL.md index a75dc25..31f6e67 100644 --- a/skills/data-warehouse-modeling/SKILL.md +++ b/skills/data-warehouse-modeling/SKILL.md @@ -1,129 +1,132 @@ --- name: data-warehouse-modeling description: > - 数仓建模实战方法论。Use when: (1) 设计分层架构 (ODS/DWD/DWS/ADS 或 Bronze/Silver/Gold) - (2) 维度建模——事实表、维度表、星型模型 (3) 指标体系 (4) 主题域划分、总线矩阵 - (5) 命名规范、词根词典 (6) 建模评审 (7) 选方法论——Kimball/Inmon/Data Vault/OneData/Medallion + 数仓建模实战方法论——不仅覆盖电商,更侧重制造业、医疗、政务、能源、农业等数字化转型行业。 + Use when: (1) 设计分层架构 (2) 维度建模 (3) 指标体系 (4) 主题域划分 + (5) 命名规范 (6) 建模评审 (7) 选方法论——Kimball/Inmon/Data Vault/OneData/Medallion (8) 湖仓一体 (9) 实时数仓 (10) SCD 处理 (11) 反模式排查 (12) dbt 工程化 - (13) 云平台数仓——Snowflake/BigQuery/Databricks/Redshift (14) 数据治理——GDPR/CCPA。 - 用户说"帮我建张表""这个指标怎么定义""数仓怎么分"时触发。 + (13) 云平台 (14) 数据治理 (15) 行业数仓——制造/医疗/政务/能源/农业/零售 + (16) IoT 时序数据处理 (17) 非结构化数据建模 (18) OT/IT 融合。 + 用户说"建张表""指标怎么定义""数仓怎么分""制造业数仓怎么建"时触发。 --- # 数仓建模 -基于 Kimball、Data Vault 2.0、阿里 OneData、Databricks Medallion 四套主流方法论的实战建模指南。 -不是教科书摘要——是踩过坑之后的判断框架。 - ## 核心约束 -这几条不是"建议",是经验教训换来的硬边界。但在说"绝不"之前,先想清楚你面对的约束。 - -**1. 数据单向流动,但允许受控的快捷路径** -- 标准路径:ODS → DWD → DWS → ADS,逐层加工 -- 现实中 ADS 偶尔需要读 DWD(如敏捷看板要明细数据),可以,但必须在 ADS 层注释标注原因,且不得成为常态 -- 绝对禁止:ADS 直接读 ODS +**1. 数据单向流动,允许受控快捷路径** +- 标准层:ODS → DWD → DWS → ADS +- ADS 偶尔读 DWD 可以,必须注释原因,不能成常态 +- 禁止 ADS 直接读 ODS -**2. 公共计算只做一次——但"公共"的判定标准是复用次数** -- 3 个以上下游消费的计算,必须下沉到 DWS -- 只有 1-2 个下游时,允许在 ADS 各自计算,但文档中标注口径一致性责任人 -- 新建公共指标的前提:已存在至少 2 个重复实现 +**2. 公共计算只做一次——"公共"标准是复用次数** +- 3 个以上下游消费 → 必须下沉到 DWS +- 1-2 个下游 → 允许在 ADS 各自计算,标注口径责任人 **3. 每张事实表必须声明粒度** -- 粒度声明不是表注释里写一行就完了——是 DDL 上方的 block comment,写清楚"一行代表什么" -- 混合粒度是事实表设计中最常见的致命错误,没有之一 -- 拿不准粒度时,选最细粒度。聚合总是安全的,拆分总是痛苦的 +- 写在 DDL 上方的 block comment 里,"一行代表什么" +- 混合粒度是事实表设计中最常见的致命错误 +- 拿不准粒度时选最细——拆分永远比聚合痛苦 **4. 原子指标口径唯一** -- 同一个"支付金额",全数仓只能有一个 SQL 定义 -- 口径分歧不是技术问题,是组织问题。解决不了组织问题,数仓分层再漂亮也是白搭 -- 先把原子指标的定义权收归数据团队,再做分层 +- 同一个"支付金额"全数仓只有一个 SQL 定义 +- 口径分歧是组织问题,不是技术问题 +- 先把定义权收归数据团队,再做分层 -**5. 维度必须统一,但不必一步到位** -- 先统一核心维度(用户、商品、时间、地域),业务维度允许各域自行管理 -- 一致性维度的建设进度,直接决定跨域分析能走多远 -- dim_user 表如果两个团队各维护一份,跨域分析就不要想了 +**5. 维度统一,不必一步到位** +- 先统一核心维度(用户/商品/时间/地域) +- 一致性维度建设进度 = 跨域分析能走多远 -**6. 命名规范比你想的更重要** -- 不是为了好看,是为了让新入职的人 3 天内能看懂表结构 -- 词根词典的核心价值:减少命名歧义带来的沟通成本 -- 一旦规范确定,严格执行。宁可改 10 个表名,不留 1 个特例 +**6. 命名规范是新人的加速器** +- 目标:新来的人 3 天内看懂表结构 +- 词根词典的核心价值:减少沟通成本 -## 方法论选择——从现实约束出发 +## 方法论选择——从约束出发 -别选"最好的"方法论,选你的团队能落地的。 +决策顺序:先看行业特性 → 再看团队规模 → 最后选方法论。 -| 你面对的现实 | 踏实的做法 | 为什么 | -|------------|-----------|-------| -| 团队 5 人以下,业务跑得快 | Kimball 星型模型,先出东西再说 | 6 种方法论里最快见效的 | -| 公司要求数据口径统一,但各业务线各搞各的 | OneData 指标体系 + Kimball 建模 | 不上指标体系,口径问题无解 | -| 金融/医疗,数据变更必须可追溯 | Data Vault 2.0 做整合层,Kimball 做报表层 | Vault 的 insert-only 天然满足审计,但报表层还是 Kimball 好用 | -| 已有数据湖,想加 BI 能力 | Medallion (Bronze/Silver/Gold) | 不用搬数据,在湖上建层 | -| 有 dbt 工程师,追求工程化 | dbt + 星型模型 | 版本控制、测试、文档一条龙 | -| 历史包袱重,几十个老旧 ODS 表 | 先做 DWD 清洗层,别动 ODS | 稳住底层再往上走 | +### 按行业选择 -→ `references/methodology-comparison.md` — 方法论详细对比和混合架构 +| 行业 | 推荐方法论 | 理由 | +|------|-----------|------| +| 互联网/电商(成熟) | Kimball 星型 | 已经很成熟,快速出活 | +| 制造业 | Data Vault 2.0 + Kimball | 多源整合 + IoT 进湖 + Kimball 出报表 | +| 医疗 | Inmon 3NF + Kimball mart | 临床数据关系复杂,先范式再集市 | +| 政务 | MDM 主数据 + 主题库 | 核心不是分析,是跨部门共享 | +| 能源/电力 | Data Vault + Lakehouse | 海量时序数据先进湖,再按场景建集市 | +| 实体零售 | Kimball + 空间维度 | 门店分析核心,空间维比用户维重要 | +| 农业 | Kimball + TSDB | 传感器用专用时序库,经营分析用 Kimball | +| 金融 | Inmon 3NF + 合规层 | 监管驱动,数据模型必须以规范化为优先 | -## 分层架构 +### 按团队规模选择 + +| 团队规模 | 能做什么 | 别碰什么 | +|---------|---------|---------| +| 5 人以下 | Kimball 星型,先出东西 | Data Vault,维护成本扛不住 | +| 5-20 人 | Kimball + 部分 OneData 指标体系 | 完整 Inmon,工期太长发不出货 | +| 20+ 人且多业务线 | Data Vault + Kimball 混合 | — | -四套主流分层模式,本质做的事一样,叫法不同: +→ `references/methodology-comparison.md` — 四种方法论详细对比 +→ `references/industry-patterns.md` — 制造/医疗/政务/能源/农业/零售行业建模模式 + +## 分层架构 | 模式 | 分层 | 来源 | 适合谁 | |------|------|------|-------| -| 国内标准 | ODS → DWD → DWS → ADS (+DIM) | 阿里 OneData | 国内互联网公司 | -| Medallion | Bronze → Silver → Gold | Databricks | 湖仓一体、Delta Lake | -| dbt | sources → staging → intermediate → marts | dbt Labs | 现代数据栈团队 | -| Data Vault | Raw Vault → Business Vault → Info Marts | Dan Linstedt | 强审计行业 | +| 国内标准 | ODS → DWD → DWS → ADS (+DIM) | 阿里 OneData | 互联网、制造业、能源 | +| Medallion | Bronze → Silver → Gold | Databricks | 湖仓一体 | +| dbt | sources → staging → marts | dbt Labs | 现代数据栈 | +| Data Vault | Raw Vault → Business Vault → Info Marts | Dan Linstedt | 金融、医疗、多源整合 | +| 政务标准 | ADR → ODS → DWD → DWS → ADS | 政务数据治理 | 政务 | 跨模式映射:Bronze ≈ ODS, Silver ≈ DWD, Gold ≈ DWS+ADS, staging ≈ DWD, marts ≈ DWS+ADS -→ `references/layer-architecture.md` — 各层职责、5 家企业对比、分层原则 +→ `references/layer-architecture.md` — 各层职责、企业对比、跨模式映射 ## 维度建模要点 -Kimball 四步法的核心不是四个步骤本身,而是步骤的顺序。 - -**第一步永远是选业务过程,不是选维度。** 先确定"我要分析什么事件"(下单、支付、退款),再问"从哪些角度分析"。搞反了就是灾难。 +Kimball 四步法的核心是步骤的顺序——先选业务过程,再选维度。搞反了就是灾难。 -**粒度声明必须具体到让 SQL 能写出来。** "订单粒度"不够——要写清楚是"每笔订单"还是"每笔订单中的每个商品行"。粒度决定了事实表能不能正确聚合。 +**粒度声明必须具体到 SQL 能写出来。** "订单粒度"不够——写清楚"每笔订单中的每个商品行"。 -**事实表设计的选择:** +**事实表选择:** - 事务事实表:记录事件发生。适合大多数场景 -- 周期快照表:每天/每小时拍一张状态快照。适合余额、库存这类状态型数据 -- 累积快照表:一条记录贯穿一个业务全流程。适合有明确里程碑的流程(下单→支付→发货→签收) +- 周期快照表:每天/每小时拍状态快照。适合余额、库存 +- 累积快照表:一条记录贯穿全流程。适合有明确里程碑的流程 -**维度退化的取舍:** -- DWD 层高频维度退化到事实表,减少 JOIN,这是正确做法 -- 但退化的是"查询时一定需要"的属性,不是"万一有用"的属性 -- 退化维度变了怎么办?DWD 层接受用当时的值。历史追溯靠 DIM 层 +**SCD 选择:** SCD2(拉链表)是历史追溯的唯一正确答案。SCD3 教科书里常见,实际几乎不用。 -**SCD 选择:** -- 需要历史追溯 → SCD2(拉链表),没有例外 -- 不需要历史 → SCD1 直接覆盖 -- SCD3(只保留前后两个值)在实际项目中几乎不用,别被教科书忽悠 +**维度退化的取舍:** 退化"查询时一定需要"的属性,不是"万一有用"的属性。 ## 指标体系 -阿里 OneData 的核心贡献不是技术框架,是**指标定义的标准化流程**。 - ``` 原子指标 = 业务过程 + 度量 - 例:支付成功的订单金额 - 派生指标 = 原子指标 + 业务限定 + 时间窗口 + 统计粒度 - 例:近 7 天母婴品类按省份的支付金额 - 复合指标 = 派生指标之间的运算 - 例:客单价 = 支付金额 / 支付用户数 ``` -**关键认知:** 原子指标的口径对齐是数据治理中最难的部分,没有捷径。业务方说"GMV"时,你至少要追问三个问题:含不含退款?含不含优惠?统计时间截止点是什么?把这三个问题的答案写进指标定义文档,比建任何模型都重要。 +原子指标的口径对齐是数据治理中最难的环节。业务方说"GMV"时至少要追问:含不含退款?含不含优惠?统计截止时间点?把这三个答案写进指标定义文档。 + +## 传统行业特有的数据格式 + +电商数仓的数据绝大部分是结构化数据。传统行业没这么幸运: + +| 数据类型 | 出现的行业 | 处理策略 | +|---------|-----------|---------| +| 时序数据 | 制造/能源/农业 | TSDB + 数仓分层聚合,原始流不进 Hive | +| 空间数据(GIS) | 政务/零售/农业 | 维度表加 GIS 列,事实表关联空间维 | +| 图数据 | 政务/供应链 | DWD 用点-边表,可引入图库做关联分析 | +| 非结构化 | 医疗(影像)/政务(文档) | 数仓存元数据+路径,AI 提取标签供分析 | +| 层级递归 | 制造(BOM)/所有行业 | 桥接表预计算展开路径,别用递归 CTE | ## 参考文件索引 | 文件 | 内容 | |------|------| | `references/methodology-comparison.md` | 四种方法论详细对比、决策树、混合架构 | -| `references/layer-architecture.md` | 各层职责、5 家企业对比 | +| `references/industry-patterns.md` | 制造/医疗/政务/能源/农业/零售行业建模模式 | +| `references/layer-architecture.md` | 各层职责、五架构对比、跨模式映射 | | `references/subject-domains.md` | 12 个主题域、业务过程 | | `references/bus-matrix.md` | 总线矩阵设计、维度 DDL | | `references/naming-conventions.md` | 表/字段命名、词根词典、数据类型 | @@ -132,18 +135,18 @@ Kimball 四步法的核心不是四个步骤本身,而是步骤的顺序。 | `references/antipatterns.md` | 反模式排查(P0/P1/P2 分级) | | `references/dbt-practices.md` | dbt 工程化完整指南 | | `references/cloud-platform-practices.md` | Snowflake/BigQuery/Databricks/Redshift | -| `references/data-governance.md` | GDPR、CCPA、数据分级、PII 脱敏 | +| `references/data-governance.md` | GDPR、CCPA、PII、数据分级 | | `references/dw-doc-standards.md` | 设计文档模板、评审清单 | ## 建模工作流 ``` -1. 业务调研 → 别急着画架构图,先搞清楚业务方到底要什么 -2. 架构设计 → 选方法论、划分主题域、画总线矩阵 -3. 规范定义 → 词根词典、命名规范、指标口径(这步不能省) -4. 模型设计 → ODS 镜像 → DWD 粒度+退化 → DWS 汇总 → ADS 服务 -5. 评审 → 粒度、跨层、主键、口径、命名 -6. 上线运维 → 质量监控、血缘、SLA +1. 业务调研 → 先搞清楚行业特性,别急着画架构图 +2. 架构设计 → 按行业选方法论、划分主题域、画总线矩阵 +3. 规范定义 → 词根词典、命名规范、指标口径 +4. 模型设计 → ODS 镜像 → DWD 粒度+退化 → DWS 汇总 → ADS 服务 +5. 评审 → 粒度、跨层、主键、口径、命名 +6. 上线运维 → 质量监控、血缘、SLA ``` -**调研阶段的实际操作:** 拿到需求后,不要先想"怎么建模"。先跟业务方确认三件事:决策场景是什么、要看的指标有哪些、数据从哪来。这三件事确认清楚了,分层和模型设计自然浮出水面。 +传统行业第一步多一个动作:先画数据全景图——哪些系统有数据、什么格式、谁负责。这一步找到的数据源往往比想象的多,也往往比想象的乱。 diff --git a/skills/data-warehouse-modeling/references/antipatterns.md b/skills/data-warehouse-modeling/references/antipatterns.md index c2eae9d..89ee255 100644 --- a/skills/data-warehouse-modeling/references/antipatterns.md +++ b/skills/data-warehouse-modeling/references/antipatterns.md @@ -3,105 +3,80 @@ ## P0 — 必须立即修复 ### AP-01:事实表混合粒度 -**表现:** 一张表里同时有订单级和订单行级数据。 -**后果:** 聚合结果直接错误,而且错误不容易发现——SUM 会多算,COUNT 看起来正常。 -**修复:** 拆成两张事实表,各自粒度明确。粒度拿不准时选最细。 +不同粒度数据混在一张表里,聚合结果必然出错。 ### AP-02:ADS 直接读 ODS -**表现:** 应用层 SQL 里 `FROM ods_xxx`。 -**后果:** 绕过了所有清洗和质量保障。ODS 里的脏数据、重复数据、格式问题全部暴露给下游。 -**修复:** 断掉这条链路,重写为读 DWD 或 DWS。如果确实有明细数据需求,在 ADS 层建视图引用 DWD。 +绕过所有清洗和质量保障。 ### AP-03:指标口径分裂 -**表现:** "支付金额"在 3 个 ADS 表里各算一遍,SQL 还不一样。 -**后果:** 三个报表三个数,业务方不再信任数据。 -**修复:** 下沉到 DWS 层统一计算。但真正的难点不是技术——是让业务方同意用哪个口径。先搞定口径共识,再改 SQL。 +同一指标在多个表各算各的。三个报表三个数。 ### AP-04:事实表无主键 -**表现:** `SELECT COUNT(*) != COUNT(DISTINCT 业务键)`。 -**后果:** 重跑 ETL 后数据翻倍,指标多计。而且没人能发现问题,因为没有唯一性校验。 -**修复:** 加主键约束(或至少加唯一性测试)。dbt 里配 `unique + not_null` 测试。 +重跑 ETL 后数据翻倍,没人发现。 -## P1 — 短期内必须处理 +### AP-05:源数据不做 Profile 就直接建模 +**传统行业特有的高危反模式。** 制造业/医疗/政务的数据源比电商脏得多——同一个"客户名称"在三个系统里可能是三个值、字段类型不一致、关键字段缺失率 30%。不做 Data Profiling 就开始建模,后期重构成本是建模的数倍。 + +**修复:** ODS 接入后,先跑 Profile 脚本(字段缺失率、值分布、异常值),把问题清单过一遍再动手写 DDL。 -### AP-05:数据烟囱 -**表现:** 相同业务逻辑在多个 ADS 表中重复实现。 -**后果:** 资源浪费,口径不一致(不是"可能"不一致,是"迟早"不一致)。 -**修复:** 3 个以上下游共用的计算,必须下沉到 DWS。 +## P1 — 短期内必须处理 -### AP-06:ODS 层做了业务逻辑 -**表现:** ODS 的 ETL 里写了 CASE WHEN、类型转换、关联计算。 -**后果:** 丧失了数据可追溯性。出了问题无法和源系统对账。 -**修复:** ODS 只做格式转换和去重。所有业务逻辑移到 DWD。 +### AP-06:数据烟囱 +相同逻辑在多个表重复实现。迟早不一致。 -### AP-07:SCD 没处理 -**表现:** 用户从"普通会员"变成"金卡会员"后,所有历史订单的用户等级字段也跟着变了。 -**后果:** 历史分析失真。"去年金卡会员的订单金额"这个数字毫无意义。 -**修复:** 需要历史追溯的维度属性用 SCD2(拉链表)。维度数据量小的话,全量快照也行。 +### AP-07:ODS 层做了业务逻辑 +丧失数据可追溯性。出问题没法跟源系统对账。 -### AP-08:万能宽表 -**表现:** 一张表 500+ 字段,混合了 10 个不同粒度的指标。 -**后果:** 维护极难,没人敢改(怕影响其他字段),字段含义模糊(新来的人看不懂)。 -**修复:** 核心 + 扩展分离。核心字段(20-30 个)稳定不变,扩展字段独立维护。或者按业务过程拆表。 +### AP-08:SCD 没处理 +历史分析失真。 -### AP-09:SELECT * -**表现:** `INSERT INTO dwd_xxx SELECT * FROM ods_xxx`。 -**后果:** 下游依赖了不该依赖的字段,源系统改个字段名就大面积报错。列裁剪优化也失效了。 -**修复:** 显式声明每个字段。哪怕只是透传。 +### AP-09:万能宽表 +500+ 字段,没人敢改,新人看不懂。 ### AP-10:维度碎片化 -**表现:** 两个团队各维护一份 dim_user,字段含义还不一样。 -**后果:** 跨域分析不可能。交易域的"活跃用户"和流量域的"活跃用户"是两个数。 -**修复:** 建立公共维度层,每个维度只有一张主维表。至少先统一 dim_user 和 dim_product。 +两个团队各维护一份 dim_user,跨域分析无望。 -## P2 — 建议优化 +### AP-11:SELECT * +源系统改字段就大面积报错。 + +### AP-12:用递归 CTE 处理层级数据 +**制造业特有错误。** BOM 配方、组织架构、设备树——这些层级结构用递归 CTE 在大数据量下性能极差。用预计算的桥接表代替递归查询。 -### AP-11:NULL 语义不明 -**表现:** `discount_amt = NULL` 不知道是"没折扣"还是"数据缺失"。 -**修复:** "没折扣"用 0,"数据缺失"用 NULL。写进字段注释。 +### AP-13:时序数据直接入 Hive +**制造业/能源/农业特有错误。** 把秒级传感器数据直接导入 Hive 后按天分区——小文件问题、查询慢、分区开销大于查询本身。用 TSDB 存原始数据,Hive 只存聚合结果。 -### AP-12:分区粒度过细 -**表现:** 按小时 + 业务线分区,产生 10 万个小文件。 -**修复:** 根据实际查询模式设计分区。大多数场景按天分区就够了。 +### AP-14:先把合规当"以后再加" +**医疗/政务/金融特有错误。** 等模型建好了再加脱敏、审计、分类分级——结果整个数仓要重构。合规要求必须在 ODS 层就开始落地。 -### AP-13:SELECT 前不聚合 -**表现:** 先 JOIN 亿级表再 GROUP BY,而不是先聚合再 JOIN。 -**修复:** Filter early, aggregate early, join late。 +## P2 — 建议优化 -### AP-14:命名无意义 -**表现:** `dw_data_new_v2_final_20240101`。 -**修复:** 遵循命名规范。没有规范?先建规范再建表。 +### AP-15:NULL 语义不明 +"没折扣"用 0,"数据缺失"用 NULL。 -### AP-15:硬编码表名 -**表现:** dbt 模型里 `FROM prod.dwd.dwd_order_d`。 -**修复:** `FROM {{ ref('dwd_order_d') }}`。保持血缘。 +### AP-16:分区粒度过细 +10 万个小文件比查询本身还慢。 -### AP-16:缺少增量策略 -**表现:** 百亿级事实表每次全量重算。 -**修复:** 设计增量分区策略。只处理新增和变更数据。 +### AP-17:过深的 JOIN 链 +7 层 JOIN 才能出报表。DWD 做维度退化。 -### AP-17:无数据质量监控 -**表现:** 数据质量问题被下游用户发现,而不是数据团队。 -**修复:** 至少配置行数波动告警(±30%)、NULL 率监控、主键唯一性检查。 +### AP-18:维度表没有代理键 +业务键变更时所有事实表都要更新。 -### AP-18:事实表存维度属性 -**表现:** fct_order 里存了 user_city、item_brand。 -**后果:** 维度变更后,要么历史数据失真,要么要重刷大量分区。 -**修复:** 维度属性放 dim 表,事实表只存外键。DWD 层做维度退化是合理的,但退化的是查询必需的属性。 +### AP-19:缺少增量策略 +百亿级事实表每次全量重算。 -### AP-19:过深的 JOIN 链 -**表现:** 7 层 JOIN 才能出报表。 -**修复:** DWD 层做维度退化,DWS 层做宽表预关联。 +### AP-20:无数据质量监控 +质量问题被下游发现,不是数据团队。 -### AP-20:维度表没有代理键 -**表现:** 事实表直接用业务自然键做外键。 -**修复:** 维度表加代理键(sk)。业务键变更时代理键不受影响。 +### AP-21:缺乏跨域沟通 +**传统行业特有。** 制造业里 OT(操作技术)团队和 IT(信息技术)团队各说各话,数仓设计时只跟 IT 沟通忽略了 OT——结果上线后发现设备数据结构和预想的完全不一样。建模前必须拉上 OT 和 IT 两方一起画数据全景图。 ## 排查优先级 -看到数仓出问题时,按这个顺序排查: +按顺序排查: 1. 主键是否唯一(AP-04)— 不唯一 = 数据不可信 2. 粒度是否一致(AP-01)— 不一致 = 聚合一定错 3. 有没有跨层引用(AP-02)— 有 = 质量没保障 4. 指标口径是否统一(AP-03)— 不统一 = 数字打架 -5. 其余的按 P1 → P2 逐步清理 +5. **源数据是否做过 Profile(AP-05)**— 没做 = 后续全是补丁 +6. 剩下的按 P1 → P2 逐步清理 diff --git a/skills/data-warehouse-modeling/references/industry-patterns.md b/skills/data-warehouse-modeling/references/industry-patterns.md new file mode 100644 index 0000000..fea782f --- /dev/null +++ b/skills/data-warehouse-modeling/references/industry-patterns.md @@ -0,0 +1,241 @@ +# 行业数仓建模 + +电商数仓的成熟度是特例,不是常态。以下行业没有电商那么完善的最佳实践,需要根据行业特性做关键设计决策。 + +## 方法论按行业选择 + +别想着用一套方法论覆盖所有行业。行业特性直接决定选型: + +| 行业 | 推荐方法论 | 核心理由 | +|------|-----------|---------| +| 制造业 | Data Vault 2.0 + Kimball | 多源系统整合(ERP/MES/SCADA),IoT 数据进湖再用 Kimball 出报表 | +| 医疗 | Inmon 3NF + Kimball mart | 临床数据关系复杂,先做企业级 3NF 模型再出科室级集市 | +| 政务 | MDM 主数据 + 主题库 | 核心需求不是分析而是跨部门数据共享和主数据统一 | +| 能源/电力 | Data Vault + Lakehouse | 海量时序数据先进数据湖,再按业务场景建 Kimball 集市 | +| 实体零售 | Kimball + 空间维度 | 门店级分析是核心,空间维度比用户维度更重要 | +| 农业 | Kimball + TSDB | 传感器数据用专用时序库,经营分析用 Kimball | +| 金融 | Inmon 3NF + 合规层 | Basel III 等监管要求决定了数据模型必须以规范化为第一优先级 | + +## 制造业 + +### 和电商的本质区别 + +电商数据是交易驱动的——下订单、支付、发货,条理清楚。制造业的数据来源复杂得多: +- ERP 里的工单和 BOM +- MES 里的生产执行记录 +- SCADA/PLC 里的设备传感器数据 +- QMS 里的质检结果 +- WMS 里的库存移动 + +多系统整合是制造数仓最难的地方。不是技术问题——每个系统有自己的数据字典,一个"生产批次号"在 ERP 和 MES 里可能含义不同。 + +### BOM 层级建模 + +BOM 不是扁平的 SKU——它是一棵树。一台设备由组件组成,组件由零件组成,零件由原材料组成。 + +**桥接表方案(推荐):** + +```sql +-- dim_bom_bridge:预计算所有层级路径 +-- 一个 BOM 树的全部父子关系展开 +CREATE TABLE dim.dim_bom_bridge ( + parent_material_id BIGINT COMMENT '父物料', + child_material_id BIGINT COMMENT '子物料', + level_diff INT COMMENT '层级差:1=直接子件,2=孙子件,...', + root_material_id BIGINT COMMENT '顶层物料(最终产品)', + is_leaf TINYINT COMMENT '是否叶子节点' +); + +-- 查询某个成品的所有零件: +-- SELECT * FROM dim_bom_bridge WHERE root_material_id = ? ORDER BY level_diff; +``` + +**不要用递归 CTE**——BOM 一般 5-10 层,但每次查询都递归,大数据量下性能扛不住。桥接表预计算是一劳永逸的方案。 + +### 设备维度 + +ISA-95 定义了设备五层结构。对应到设备维度: + +``` +企业 → 工厂 → 区域 → 产线 → 工位 → 设备 +``` + +在 dim_equipment 里建层级列即可(工厂 ID、产线 ID、工位 ID),不用专门的层级表。层级不超过 5 层,维度退化到表里足够。 + +### IoT 时序数据处理 + +传感器数据的高频特点决定了它不能直接入传统数仓。 + +**混合架构:** TSDB(时序数据库)+ 数仓 + +- 原始秒级/毫秒级数据 → TDengine / TimescaleDB / InfluxDB +- 分钟/小时窗口聚合 → ODS 层存原始流(Parquet/ORC 格式,按天分区) +- DWD 层存小时级聚合,面向分析 +- 实时异常检测 → Flink 直接消费 Kafka,不进数仓 + +不要试图把秒级传感器数据全部入 Hive——一天几十亿行数据,分区的开销比查询还大。 + +### 质量维度 + +制造数仓特有的 `dim_quality` 不是电商的"好评/差评"——是不合格品数、缺陷代码(行业标准编码)、工艺参数偏差值。质检结果表的粒度是"一次检测",可能会生成多条数据(外观检测一个结果、尺寸检测另一个结果)。 + +## 医疗 + +### 医疗数据和电商数据的根本不同 + +电商的"一笔交易"是清晰的:用户下了一个订单。医疗没有"一笔交易"的概念——一次就诊包含挂号、问诊、检查、处方、取药等多个不可拆分的事件。 + +**就诊集模型(episode-driven):** 数仓需要同时支持三个粒度的分析: +- Progress 粒度(一次检查、一次用药) +- Visit 粒度(一次就诊) +- Episode 粒度(一次疾病的完整诊疗过程) + +这三个粒度的事实表不是替代关系,是共存关系——不同分析场景需要不同粒度。 + +### FHIR → 星型模型映射 + +HL7 FHIR 是医疗互操作的主流标准。资源到维度/事实的映射: + +| FHIR 资源 | DW 表类型 | DW 表名 | +|----------|----------|---------| +| Patient | 维度 | dim_patient | +| Practitioner | 维度 | dim_practitioner | +| Organization | 维度 | dim_organization | +| Encounter | 事实 | fact_encounter | +| Observation | 事实 | fact_observation | +| MedicationRequest | 事实 | fact_medication_order | +| Condition | 事实 / 维度 | fact_condition / dim_condition | +| Procedure | 事实 | fact_procedure | + +**结构化问题:** FHIR 是 EAV(实体-属性-值)结构——一个 Observation 可以代表血压、体重、血糖检测结果,字段差别巨大。DW 里要么做宽表(按 Observation type 拆成多张事实表),要么用 JSON 存值。拆表是更好的选择——查询性能和语义清晰度都好。 + +### 隐私和合规 + +医疗数仓的合规要求比电商严格得多: +- 脱敏不是事后加的——ODS 入库时就要做 +- 患者姓名 → 哈希,身份证号 → 掩码,手机号 → 脱敏 +- 删除权不是把数据删了就行——临床数据有法定保存期限(中国 30 年),不能真删。用 SCD2 的 end_dt 标记"被遗忘"状态,不显示但保留 + +## 政务 + +### 政务数仓的本质 + +政务数仓不是给内部 BI 用的——首要功能是**跨部门数据共享**。分析是第二位的。 + +这意味着 ODS 层不是简单的"原样保留"——需要加数据来源标记(来自哪个委办局)、对接时间、数据责任方。政务数仓的 ODS 层更像一个数据交换中心,每一行数据都要标注"谁提供的、什么时候提供的、什么版本"。 + +### 三库架构 + +政务的"主题域"不是电商式的 12 个主题域,而是三大基础库 + N 个专题库: + +``` +基础库(必须有): + 人口库 — 身份证号关联公安/民政/人社/卫健/教育 + 法人库 — 统一社会信用代码关联工商/税务/统计/金融 + 自然资源与空间地理库 — 空间坐标关联国土/规划/环保/水利 + +主题库(按需建设): + 卫健、社保、教育、交通、生态环境... + +专题库(应用驱动): + 一网通办、领导驾驶舱、应急指挥... +``` + +### 分层架构(和电商不同) + +政务使用的是 ADR → ODS → DWD → DWS → ADS 六层: + +- ADR(归集层)——各部门原始数据,按来源存储,不做任何处理 +- ODS——统一接入后的贴源层,加来源标识和时间戳,不做清洗 +- DWD——多源融合去重后的标准层 + +这个 ADR 层是政务特有的——因为数据来自不同委办局,格式、编码、口径全不一样,需要一个"数据湖"层先存起来,再逐步清洗。 + +### 合规 + +- 《数据安全法》三级分类(一般/重要/核心)决定数据能否出域 +- 等保 2.0 决定了安全架构 +- 国密算法(SM2/SM3/SM4)是硬性要求 +- 跨部门数据共享需要"可用不可见"——联邦学习或隐私计算 + +## 能源/电力 + +### 数据量是首要问题 + +智能电表 15 分钟采集一次 × 1000 万户 × 96 次/日 = 近 10 亿行/天 = 3500 亿行/年。 + +降采样是必须的,不是可选的。策略: + +``` +原始秒级 → 分钟级聚合(保留 7 天) +分钟级 → 15 分钟级聚合(保留 30 天) +15 分钟级 → 小时级聚合(保留 1 年) +小时级 → 日级聚合(保留 3-5 年) +``` + +不同粒度的聚合表在不同的存储层——秒级在 TSDB,日级在 Hive/数仓。 + +### 电网拓扑维度 + +和电商的用户地域维度不同,供电维度是物理拓扑: + +``` +供电局 → 变电站 → 线路 → 台区 → 用户 +``` + +这个维度不是"城市 → 区县 → 街道"——电网拓扑和行政区划是重叠但不一致的。电网上的一条线路可能跨越多个行政区。 + +### 线损分析 + +能源特有的指标:供电量 - 售电量 = 线损。 + +DWS 层按台区 × 日度汇总供电量和售电量,然后计算线损率。线损率的波动是电网运营最关注的指标之一。正常情况下线损率应该在 3%-7%,高于这个值可能是偷电或设备老化。 + +## 实体零售(非电商) + +核心差异:电商靠 Cookie/设备 ID 识别用户,实体零售靠会员卡。这意味着用户数据完整度差了不止一个数量级——到店消费的 80% 以上是非会员。 + +空间维度取代了用户维度成为最核心的分析角度: +- dim_store 必须有 GIS 坐标 +- 商圈 → 城市 → 大区 的空间上卷 +- 库存维度不是简单的"仓库库存"——是 DC → 门店 → 货架三级 + +## 农业 + +和制造业最像——传感器数据是核心输入。但有一个电商数仓不存在的维度: + +**物候维度**——农作物的生长阶段(播种→出苗→分蘖→灌浆→成熟→收获),这不是日历日期能定义的。需要一张物候维表,标明每种作物在各地的标准物候期。事实表关联物候维度和日历维度两个时间维。 + +溯源全链路(种子→种植→采收→加工→流通→零售)是农业特有的需求。和供应链数仓的 traceability 模式一致——需要全链路的批次关联表。 + +## 跨行业共性 + +### 数据格式多样化 + +电商数仓的数据基本都是结构化数据——MySQL 表、日志文件。传统行业没有这么幸运: + +| 数据类型 | 处理策略 | +|---------|---------| +| 时序数据(制造/能源/农业) | TSDB + 数仓分层聚合,原始数据不进 Hive | +| 空间数据(政务/零售/农业) | 维度表加 GIS 列(PostGIS/GeoParquet),事实表关联空间维 | +| 图数据(政务/供应链) | DWD 层用点-边表存储关系,可引入图数据库做关联分析 | +| 非结构化(医疗影像/政务文档) | 数仓只存元数据+文件路径,AI 流水线提取结构化标签供分析 | + +### 合规是硬约束 + +电商数仓把隐私合规放在数据治理环节,传统行业没这么轻松——合规从 ODS 层就开始: + +- 制造业:ISA-95 决定设备数据归属 +- 医疗:HIPAA/个人信息保护法决定数据如何使用 +- 政务:数安法三级分类决定数据能否出域 +- 能源:电网拓扑数据属于国家关键信息基础设施 + +设计传统行业数仓时,合规要求不是"以后再加"的事项。选型阶段就要考虑——数据能不能出本地、需要什么加密算法、审计日志怎么存。 + +### OT/IT 融合 + +制造业、能源、农业的共同难题:OT(操作技术)系统(PLC、SCADA、传感器)和 IT(信息技术)系统(ERP、BI)是两套完全不同的语系。 + +OT 系统关心的是实时状态("设备转速 1500rpm"、"温度 75°C"),IT 系统关心的是经营指标("设备利用率"、"产线产出")。 + +这两套数据要在 DWD 层融合——把"转速 1500rpm 持续了 3 小时"翻译成"设备运转 3 小时"。这个翻译规则不是通用的,每个设备类型都要单独定义。 diff --git a/skills/data-warehouse-modeling/scripts/sql-templates.md b/skills/data-warehouse-modeling/scripts/sql-templates.md index 463b6b6..0dc7e12 100644 --- a/skills/data-warehouse-modeling/scripts/sql-templates.md +++ b/skills/data-warehouse-modeling/scripts/sql-templates.md @@ -7,7 +7,7 @@ ```sql -- ODS 贴源层建表 -- 特点:原样保留源数据,按天分区,定义生命周期 -CREATE TABLE IF NOT EXISTS ods_mysql_trade_order_di ( +CREATE TABLE IF NOT EXISTS ods.ods_mysql_trade_order_di ( id BIGINT COMMENT '主键ID', order_id STRING COMMENT '订单编号', user_id BIGINT COMMENT '用户ID', @@ -38,7 +38,7 @@ TBLPROPERTIES ( ```sql -- DWD 明细层建表 -- 特点:清洗后标准化数据,维度退化,保持最细粒度 -CREATE TABLE IF NOT EXISTS dwd_trade_order_detail_di ( +CREATE TABLE IF NOT EXISTS dwd.dwd_trade_order_detail_di ( order_detail_id STRING COMMENT '订单明细唯一标识', order_id STRING COMMENT '订单编号', user_id BIGINT COMMENT '用户ID', @@ -80,7 +80,7 @@ TBLPROPERTIES ( ```sql -- DWD ETL: 清洗 + 维度退化 -INSERT OVERWRITE TABLE dwd_trade_detail_di PARTITION (ds = '${bizdate}') +INSERT OVERWRITE TABLE dwd.dwd_trade_detail_di PARTITION (ds = '${bizdate}') SELECT -- 生成明细唯一标识 CONCAT(o.order_id, '_', o.sku_id) AS order_detail_id, @@ -106,7 +106,7 @@ SELECT o.order_time, o.pay_time, CURRENT_TIMESTAMP() AS etl_time -FROM ods_mysql_trade_order_di o +FROM ods.ods_mysql_trade_order_di o LEFT JOIN dim_user_info_df dim_u ON o.user_id = dim_u.user_id AND dim_u.ds = '${bizdate}' LEFT JOIN dim_product_info_df dim_p ON o.product_id = dim_p.product_id AND dim_p.ds = '${bizdate}' WHERE o.ds = '${bizdate}' @@ -121,7 +121,7 @@ WHERE o.ds = '${bizdate}' ```sql -- DIM 维度表:全量快照(适合维度数据量不大的场景) -CREATE TABLE IF NOT EXISTS dim_user_info_df ( +CREATE TABLE IF NOT EXISTS dim.dim_user_info_df ( user_id BIGINT COMMENT '用户ID', user_name STRING COMMENT '用户名', phone STRING COMMENT '手机号(脱敏)', @@ -146,7 +146,7 @@ TBLPROPERTIES ('lifecycle' = '365'); ```sql -- DIM 维度表:拉链表(SCD Type 2,保留历史变更) -CREATE TABLE IF NOT EXISTS dim_product_info_zipper ( +CREATE TABLE IF NOT EXISTS dim.dim_product_info_zipper ( product_id BIGINT COMMENT '商品ID(业务键)', product_sk BIGINT COMMENT '代理键', product_name STRING COMMENT '商品名称', @@ -169,14 +169,14 @@ TBLPROPERTIES ('lifecycle' = '9999'); ```sql -- Step 1: 关闭旧记录 -INSERT OVERWRITE TABLE dim_product_info_zipper +INSERT OVERWRITE TABLE dim.dim_product_info_zipper SELECT product_id, product_sk, product_name, category1_name, category2_name, brand_name, price, status, start_dt, - CASE WHEN product_id IN (SELECT product_id FROM ods_product WHERE ds = '${bizdate}') + CASE WHEN product_id IN (SELECT product_id FROM ods.ods_product WHERE ds = '${bizdate}') THEN '${bizdate}' ELSE end_dt END AS end_dt, - CASE WHEN product_id IN (SELECT product_id FROM ods_product WHERE ds = '${bizdate}') + CASE WHEN product_id IN (SELECT product_id FROM ods.ods_product WHERE ds = '${bizdate}') THEN 0 ELSE is_current END AS is_current, etl_time FROM dim_product_info_zipper @@ -193,7 +193,7 @@ SELECT '9999-12-31' AS end_dt, 1 AS is_current, CURRENT_TIMESTAMP() AS etl_time -FROM ods_product p +FROM ods.ods_product p WHERE p.ds = '${bizdate}' ; ``` @@ -202,7 +202,7 @@ WHERE p.ds = '${bizdate}' ```sql -- DWS 汇总层:用户粒度 + 近1天交易汇总 -CREATE TABLE IF NOT EXISTS dws_trade_user_1d_df ( +CREATE TABLE IF NOT EXISTS dws.dws_trade_user_1d_df ( user_id BIGINT COMMENT '用户ID', order_cnt BIGINT COMMENT '下单次数', order_product_cnt BIGINT COMMENT '下单商品件数', @@ -226,7 +226,7 @@ STORED AS ORC; ```sql -- DWS ETL: 多表聚合 → 用户粒度汇总 -INSERT OVERWRITE TABLE dws_trade_user_1d_df PARTITION (ds = '${bizdate}') +INSERT OVERWRITE TABLE dws.dws_trade_user_1d_df PARTITION (ds = '${bizdate}') SELECT t.user_id, -- 订单指标 @@ -267,7 +267,7 @@ LEFT JOIN (...) cpn ON t.user_id = cpn.user_id ```sql -- ADS 应用层:销售日报 -CREATE TABLE IF NOT EXISTS ads_sales_daily_report ( +CREATE TABLE IF NOT EXISTS ads.ads_sales_daily_report ( stat_date STRING COMMENT '统计日期', province_code STRING COMMENT '省份编码', province_name STRING COMMENT '省份名称', @@ -286,7 +286,7 @@ COMMENT '销售日报-省份类目粒度' STORED AS ORC; -- ADS ETL -INSERT OVERWRITE TABLE ads_sales_daily_report +INSERT OVERWRITE TABLE ads.ads_sales_daily_report SELECT '${bizdate}' AS stat_date, province_code, From f254d5cb160c26863f5de48fae257e5d8713168f Mon Sep 17 00:00:00 2001 From: jnMetaCode Date: Tue, 9 Jun 2026 16:47:28 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20v6=20=E2=80=94=20=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=8C=96=E8=BD=AC=E5=9E=8B=E4=BC=81=E4=B8=9A=E6=B8=90=E8=BF=9B?= =?UTF-8?q?=E5=BC=8F=E6=95=B0=E4=BB=93=E5=BB=BA=E8=AE=BE=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 方法论选择:企业阶段 → 行业特性 → 团队规模三层次 - 数字化成熟度四阶段:初始级→部门级→企业级→数据驱动 - MDM 三阶段:Registry→Consolidation→Coexistence - 渐进式三步走:MVP→主题域覆盖→智能化 - 纸电鸿沟、遗留系统孤岛处理方案 - 三个转型行业的详细建模路径 --- skills/data-warehouse-modeling/SKILL.md | 157 +++++++++++------- .../references/industry-patterns.md | 137 +++++++++++++++ 2 files changed, 232 insertions(+), 62 deletions(-) diff --git a/skills/data-warehouse-modeling/SKILL.md b/skills/data-warehouse-modeling/SKILL.md index 31f6e67..697b1c5 100644 --- a/skills/data-warehouse-modeling/SKILL.md +++ b/skills/data-warehouse-modeling/SKILL.md @@ -1,13 +1,14 @@ --- name: data-warehouse-modeling description: > - 数仓建模实战方法论——不仅覆盖电商,更侧重制造业、医疗、政务、能源、农业等数字化转型行业。 - Use when: (1) 设计分层架构 (2) 维度建模 (3) 指标体系 (4) 主题域划分 - (5) 命名规范 (6) 建模评审 (7) 选方法论——Kimball/Inmon/Data Vault/OneData/Medallion - (8) 湖仓一体 (9) 实时数仓 (10) SCD 处理 (11) 反模式排查 (12) dbt 工程化 - (13) 云平台 (14) 数据治理 (15) 行业数仓——制造/医疗/政务/能源/农业/零售 - (16) IoT 时序数据处理 (17) 非结构化数据建模 (18) OT/IT 融合。 - 用户说"建张表""指标怎么定义""数仓怎么分""制造业数仓怎么建"时触发。 + 数仓建模实战方法论——覆盖互联网和传统行业,侧重数字化转型企业的建模策略。 + Use when: (1) 数仓分层设计 (2) 维度建模 (3) 指标体系 (4) 主题域划分 + (5) 命名规范 (6) 建模评审 (7) 选方法论 (8) 湖仓一体 (9) 实时数仓 + (10) SCD 处理 (11) 反模式排查 (12) dbt 工程化 (13) 云平台 + (14) 数据治理 (15) 行业数仓——制造/医疗/政务/能源/农业/零售 + (16) IoT 时序数据 (17) 非结构化数据 (18) OT/IT 融合 + (19) 数字化转型各阶段数仓策略 (20) 传统企业渐进式建设。 + 用户说"建张表""指标怎么定义""传统企业怎么建数仓"时触发。 --- # 数仓建模 @@ -22,16 +23,16 @@ description: > **2. 公共计算只做一次——"公共"标准是复用次数** - 3 个以上下游消费 → 必须下沉到 DWS - 1-2 个下游 → 允许在 ADS 各自计算,标注口径责任人 +- 新建公共指标前提:已存在至少 2 个重复实现 **3. 每张事实表必须声明粒度** - 写在 DDL 上方的 block comment 里,"一行代表什么" - 混合粒度是事实表设计中最常见的致命错误 -- 拿不准粒度时选最细——拆分永远比聚合痛苦 +- 拿不准粒度时选最细 **4. 原子指标口径唯一** -- 同一个"支付金额"全数仓只有一个 SQL 定义 +- 先搞定口径共识再动手写 SQL - 口径分歧是组织问题,不是技术问题 -- 先把定义权收归数据团队,再做分层 **5. 维度统一,不必一步到位** - 先统一核心维度(用户/商品/时间/地域) @@ -39,35 +40,64 @@ description: > **6. 命名规范是新人的加速器** - 目标:新来的人 3 天内看懂表结构 -- 词根词典的核心价值:减少沟通成本 +- 词根词典减少沟通成本 -## 方法论选择——从约束出发 +## 方法论选择——三层次决策 -决策顺序:先看行业特性 → 再看团队规模 → 最后选方法论。 +不要从"哪个方法论最好"出发。按这个顺序决策: -### 按行业选择 +### 第一层:企业数字化成熟度 -| 行业 | 推荐方法论 | 理由 | -|------|-----------|------| -| 互联网/电商(成熟) | Kimball 星型 | 已经很成熟,快速出活 | -| 制造业 | Data Vault 2.0 + Kimball | 多源整合 + IoT 进湖 + Kimball 出报表 | -| 医疗 | Inmon 3NF + Kimball mart | 临床数据关系复杂,先范式再集市 | -| 政务 | MDM 主数据 + 主题库 | 核心不是分析,是跨部门共享 | -| 能源/电力 | Data Vault + Lakehouse | 海量时序数据先进湖,再按场景建集市 | -| 实体零售 | Kimball + 空间维度 | 门店分析核心,空间维比用户维重要 | -| 农业 | Kimball + TSDB | 传感器用专用时序库,经营分析用 Kimball | -| 金融 | Inmon 3NF + 合规层 | 监管驱动,数据模型必须以规范化为优先 | +先问两个问题:"同一个客户在几个系统里有不同 ID?"、"数据团队多少人?" -### 按团队规模选择 +| 转型阶段 | 特征 | 能做的 | 别碰的 | +|---------|------|--------|--------| +| 初始级(Excel 驱动) | 数据靠手工,无统一系统 | ODS + 1-2 张核心报表,星型模型最小集 | Data Vault、六层架构、实时数仓 | +| 部门级(系统烟囱) | ERP/CRM 独立,主数据不统一 | Kimball 星型 + DIM 层统一主数据 | Data Mesh、全量 Lakehouse | +| 企业级(跨系统打通) | 核心系统互联,开始建数据中台 | Data Vault + Kimball 混合、ELT | — | +| 数据驱动(智能化) | 数据是产品,AI 决策日常化 | Data Mesh、Lakehouse、批流一体 | — | -| 团队规模 | 能做什么 | 别碰什么 | -|---------|---------|---------| -| 5 人以下 | Kimball 星型,先出东西 | Data Vault,维护成本扛不住 | -| 5-20 人 | Kimball + 部分 OneData 指标体系 | 完整 Inmon,工期太长发不出货 | -| 20+ 人且多业务线 | Data Vault + Kimball 混合 | — | +### 第二层:行业特性 -→ `references/methodology-comparison.md` — 四种方法论详细对比 -→ `references/industry-patterns.md` — 制造/医疗/政务/能源/农业/零售行业建模模式 +→ 详细见 `references/industry-patterns.md` + +| 行业 | 推荐方法论 | 核心原因 | +|------|-----------|---------| +| 互联网/电商 | Kimball 星型 | 已很成熟 | +| 制造业 | Data Vault + Kimball | 多源整合,IoT 进湖 + Kimball 出报表 | +| 医疗 | Inmon 3NF + Kimball mart | 临床数据关系复杂 | +| 政务 | MDM + 主题库 | 核心是跨部门共享,不是分析 | +| 能源/电力 | Data Vault + Lakehouse | 海量时序数据 | +| 农业 | Kimball + TSDB | 传感器 + 经营分析双轨 | + +### 第三层:团队规模 + +| 团队规模 | 能做 | 别碰 | +|---------|------|------| +| 1-3 人 | ODS → ADS 三层,星型模型 | Data Vault(维护成本扛不住) | +| 3-5 人 | Kimball 四层,指标体系 | Inmon(工期太长) | +| 5-10 人 | Kimball + 部分论据库 | Data Mesh | +| 10+ 人 | Data Vault + Kimball 混合 | — | + +## 渐进式建设策略 + +传统企业数仓一次建完的,从众没有见过成功的。按三步走: + +``` +第一步(1-3 个月):最小可行数据产品 + 选一个业务方每天用的报表,打通 1-2 个系统,只建 ODS → ADS 三层 + → 3 个月内出第一个可信数字,项目才能活下来 + +第二步(3-12 个月):主题域批量覆盖 + 财务 → 客户 → 供应链 → 人力 + → 这个阶段建 DIM 层和 DWS 层,命名规范和词根词典必须锁定 + +第三步(12 月+):智能化 + 批流一体、Lakehouse、ML Pipeline + → 前提是第二步的数据治理已经到位 +``` + +**第一步选场景的标准:** 数据源 ≤ 3 个系统;查询频次高(天天用所以价值感知强);计算逻辑简单(不容易出错)。财务日报是最安全的第一个场景。 ## 分层架构 @@ -79,74 +109,77 @@ description: > | Data Vault | Raw Vault → Business Vault → Info Marts | Dan Linstedt | 金融、医疗、多源整合 | | 政务标准 | ADR → ODS → DWD → DWS → ADS | 政务数据治理 | 政务 | -跨模式映射:Bronze ≈ ODS, Silver ≈ DWD, Gold ≈ DWS+ADS, staging ≈ DWD, marts ≈ DWS+ADS +**转型期最小分层:** 只建 ODS → DW(合并 DWD+DWS)→ ADS 三层。DIM 在第二阶段加。 + +## 转型期特有的数据建模决策 -→ `references/layer-architecture.md` — 各层职责、企业对比、跨模式映射 +**主数据不一致(MDM):** 不要试图一步到位消灭所有不一致。Registry(对照表,1-3 月)→ Consolidation(Golden Record,3-12 月)→ Coexistence(双向同步,12 月+)。 + +**纸电鸿沟:** 加 `source_type` 字段区分数据来源(手工/自动/传感器),`data_quality` 标记可信度。低质量数据不进核心指标。 + +**遗留系统:** CDC → Staging 层 1:1 镜像 → DWD 层再清洗。 ## 维度建模要点 -Kimball 四步法的核心是步骤的顺序——先选业务过程,再选维度。搞反了就是灾难。 +Kimball 四步法的核心是步骤顺序——先选业务过程,再选维度。搞反了就是灾难。 **粒度声明必须具体到 SQL 能写出来。** "订单粒度"不够——写清楚"每笔订单中的每个商品行"。 **事实表选择:** - 事务事实表:记录事件发生。适合大多数场景 -- 周期快照表:每天/每小时拍状态快照。适合余额、库存 -- 累积快照表:一条记录贯穿全流程。适合有明确里程碑的流程 +- 累积快照表:一条记录贯穿全流程。适合生产工单、维修工单 +- 周期快照表:每天/每小时拍状态快照。适合库存、设备状态 -**SCD 选择:** SCD2(拉链表)是历史追溯的唯一正确答案。SCD3 教科书里常见,实际几乎不用。 +**SCD 选择:** SCD2 是历史追溯唯一正确答案。SCD3 教科书里有,实际项目几乎不用。 -**维度退化的取舍:** 退化"查询时一定需要"的属性,不是"万一有用"的属性。 +**转型期特别关注:** 维度属性标注数据来源。同一个用户的手机号,CRM 里一个、ERP 里另一个——在 dim 表里记录两个来源的值,定义合并规则(来源优先级)。 ## 指标体系 ``` 原子指标 = 业务过程 + 度量 派生指标 = 原子指标 + 业务限定 + 时间窗口 + 统计粒度 -复合指标 = 派生指标之间的运算 ``` -原子指标的口径对齐是数据治理中最难的环节。业务方说"GMV"时至少要追问:含不含退款?含不含优惠?统计截止时间点?把这三个答案写进指标定义文档。 +原子指标口径对齐是数据治理最难的环节。业务方说"GMV"至少要追问三个问题:含不含退款?含不含优惠?统计截止时间点?写进指标定义文档。 ## 传统行业特有的数据格式 -电商数仓的数据绝大部分是结构化数据。传统行业没这么幸运: - -| 数据类型 | 出现的行业 | 处理策略 | +| 数据类型 | 处理的行业 | 处理策略 | |---------|-----------|---------| | 时序数据 | 制造/能源/农业 | TSDB + 数仓分层聚合,原始流不进 Hive | -| 空间数据(GIS) | 政务/零售/农业 | 维度表加 GIS 列,事实表关联空间维 | -| 图数据 | 政务/供应链 | DWD 用点-边表,可引入图库做关联分析 | -| 非结构化 | 医疗(影像)/政务(文档) | 数仓存元数据+路径,AI 提取标签供分析 | -| 层级递归 | 制造(BOM)/所有行业 | 桥接表预计算展开路径,别用递归 CTE | +| 空间数据(GIS) | 政务/零售/农业 | 维度表加 GIS 列 | +| 图数据 | 政务/供应链 | DWD 用点-边表,可引入图库 | +| 非结构化 | 医疗(影像)/政务(文档) | 数仓存元数据 + 路径,AI 提取标签 | +| 层级递归 | 制造(BOM)/所有 | 桥接表预计算,别用递归 CTE | ## 参考文件索引 | 文件 | 内容 | |------|------| | `references/methodology-comparison.md` | 四种方法论详细对比、决策树、混合架构 | -| `references/industry-patterns.md` | 制造/医疗/政务/能源/农业/零售行业建模模式 | -| `references/layer-architecture.md` | 各层职责、五架构对比、跨模式映射 | -| `references/subject-domains.md` | 12 个主题域、业务过程 | +| `references/industry-patterns.md` | 行业建模 + 转型阶段 + 渐进式建设策略 | +| `references/layer-architecture.md` | 各层职责、五架构对比 | +| `references/subject-domains.md` | 12 个主题域 | | `references/bus-matrix.md` | 总线矩阵设计、维度 DDL | | `references/naming-conventions.md` | 表/字段命名、词根词典、数据类型 | -| `scripts/sql-templates.md` | 各层 DDL/DML 模板(Hive/Spark) | -| `references/realtime-dw-design.md` | Kafka+Flink+OLAP 实时数仓 | -| `references/antipatterns.md` | 反模式排查(P0/P1/P2 分级) | -| `references/dbt-practices.md` | dbt 工程化完整指南 | +| `scripts/sql-templates.md` | 各层 DDL/DML(Hive/Spark) | +| `references/realtime-dw-design.md` | Kafka+Flink+OLAP | +| `references/antipatterns.md` | 反模式 P0/P1/P2 | +| `references/dbt-practices.md` | dbt 工程化 | | `references/cloud-platform-practices.md` | Snowflake/BigQuery/Databricks/Redshift | -| `references/data-governance.md` | GDPR、CCPA、PII、数据分级 | -| `references/dw-doc-standards.md` | 设计文档模板、评审清单 | +| `references/data-governance.md` | GDPR、CCPA、数据分级 | +| `references/dw-doc-standards.md` | 文档模板、评审清单 | ## 建模工作流 ``` -1. 业务调研 → 先搞清楚行业特性,别急着画架构图 -2. 架构设计 → 按行业选方法论、划分主题域、画总线矩阵 -3. 规范定义 → 词根词典、命名规范、指标口径 -4. 模型设计 → ODS 镜像 → DWD 粒度+退化 → DWS 汇总 → ADS 服务 +1. 业务调研 → 先判断数字化阶段,再画数据全景图(哪些系统有数据、什么格式、谁负责) +2. 架构设计 → 按阶段 + 行业 + 团队选方法论,选一个速赢场景验证 +3. 规范定义 → 词根词典、命名规范、指标口径(这一步最容易被跳过) +4. 模型设计 → ODS 镜像 → DWD 粒度 → DWS 汇总 → ADS 服务 5. 评审 → 粒度、跨层、主键、口径、命名 6. 上线运维 → 质量监控、血缘、SLA ``` -传统行业第一步多一个动作:先画数据全景图——哪些系统有数据、什么格式、谁负责。这一步找到的数据源往往比想象的多,也往往比想象的乱。 +转型期最重要的原则:**先让数据跑起来,再让数据变完美。** 三个月不出第一个可信数字,项目大概率会死。 diff --git a/skills/data-warehouse-modeling/references/industry-patterns.md b/skills/data-warehouse-modeling/references/industry-patterns.md index fea782f..b437350 100644 --- a/skills/data-warehouse-modeling/references/industry-patterns.md +++ b/skills/data-warehouse-modeling/references/industry-patterns.md @@ -239,3 +239,140 @@ DWS 层按台区 × 日度汇总供电量和售电量,然后计算线损率。 OT 系统关心的是实时状态("设备转速 1500rpm"、"温度 75°C"),IT 系统关心的是经营指标("设备利用率"、"产线产出")。 这两套数据要在 DWD 层融合——把"转速 1500rpm 持续了 3 小时"翻译成"设备运转 3 小时"。这个翻译规则不是通用的,每个设备类型都要单独定义。 + +--- + +# 数字化转型企业的数仓建设 + +## 成熟度四阶段与对数仓要求 + +传统企业的数字化转型不是一步到位的。数仓设计必须匹配企业当前阶段,超前等于浪费,滞后等于瓶颈。 + +| 阶段 | 特征 | 数仓策略 | 别做的事 | +|------|------|---------|---------| +| **阶段1:初始级** | 数据靠 Excel 传递,各部门自建小库 | 先盘点。建 ODS 把数据汇到一起,出 1-2 张核心报表。星型模型最小集 | 别急着上 Data Vault 或六层架构 | +| **阶段2:部门级** | ERP/CRM 独立运行,主数据互相不认识 | 建 DIM 层统一主数据。Kimball 星型模型,按主题域分批建 | 别试图一次打通所有系统 | +| **阶段3:企业级** | 核心系统打通,开始建数据中台 | Data Vault 做整合层 + Kimball 做集市。ELT 策略,建数据血缘 | 别用纯流处理替代批处理 | +| **阶段4:数据驱动** | 数据是产品,AI 辅助决策 | Data Mesh 领域自治,Lakehouse 统一存储,实时 + 批处理一体 | — | + +**判断当前阶段的简单方法:** 问"你们公司同一个客户在几个系统里有不同的 ID?"——3 个以上就是阶段2。 + +## 转型期特有的三个数据建模难题 + +### 遗留系统数据孤岛 + +传统企业不可能推倒重来——SAP 跑着财务、金蝶管着进销存、自研系统管着生产,每个系统都有历史数据。 + +**处理策略:** CDC(Debezium/Canal)实时捕获变更 → Staging 层 1:1 镜像隔离源系统 → DWD 层再清洗标准化。关键是在 staging 层之前不碰任何数据——保留原始形态,出问题能回溯。 + +### 主数据不一致 + +同一客户在 ERP、CRM、WMS 里是三个不同 ID——这是正常现象,不是 bug。数仓要做的不是消灭它,是管理它。 + +**MDM 三阶段路线:** + +``` +第一阶段(1-3 月):Registry —— 建对照表,只读不写 + dim_customer_xref: erp_customer_id → crm_customer_id → wms_customer_id + +第二阶段(3-12 月):Consolidation —— DIM 层建 Golden Record + dim_customer_sk(代理键)→ 关联各系统 ID → 合并规则定义 + +第三阶段(12 月+):Coexistence —— 双向同步 + 源系统变更 → 数仓更新 → 下游系统同步 +``` + +**优先做客户主数据**,其次是产品、供应商。组织架构和财务科目可以延后。 + +### 纸电鸿沟 + +仍在用纸质单据的部门,数据质量和完整性天然差。不要等纸质全部数字化再建数仓——等不起。 + +**区分数据来源标记:** + +```sql +ALTER TABLE ods.ods_xxx ADD COLUMN source_type STRING COMMENT '数据来源:manual/auto/sensor'; +ALTER TABLE ods.ods_xxx ADD COLUMN data_quality STRING COMMENT '数据可信度:high/medium/low'; +``` + +低质量数据在 ODS/DWD 层可用,但不进入 DWS/ADS 层的核心指标计算。在 ADS 层标注"数据完整度 70%,仅供参考"比硬着头皮出精确数字要诚实得多。 + +## 渐进式建设"三步走" + +不要提 Big Bang —— 传统企业数仓一次性建完的,我没见过一个成功的。 + +### 第一步:最小可行数据产品(1-3 个月) + +目标:打通 1-2 个核心系统,交付一张可信报表。 + +- 选一个业务方每天都要用的报表(日销售、库存日报) +- 只建 ODS → DW → ADS 三层 +- DWS 暂时不需要——先验证整个链路跑得通 +- 第一张报表的出数准确率达到 99% 以上再交付 + +**选场景的标准:** 数据源不超过 3 个系统;查询频次高所以价值感知强;计算逻辑简单所以不容易出错。 + +### 第二步:主题域批量覆盖(3-12 个月) + +按照"财务/经营 → 客户/会员 → 供应链 → 人力"的顺序依次建。 + +**优先顺序的逻辑:** 财务分析能直接给老板看,最容易拿到预算;客户数据打通能直接看到营销效果;供应链数据量最大但价值感知最弱,放后面。 + +这个阶段开始建 DIM 层和 DWS 层。数据治理(命名规范、词根词典、元数据管理)必须在这个阶段完成——再晚就改不动了。 + +### 第三步:智能化驱动(12 个月+) + +批流一体,Lakehouse,ML Pipeline。但这个阶段的前提是第二步的数据质量和治理已经到位。 + +我见过太多公司跳过第二步直接做"智能驾驶舱"——底层数据都是错的,上面的可视化再漂亮也没意义。 + +## 成败关键 + +### 做对的事 + +- **一把手工程** — 没有高层推动,跨部门数据共享就是空话 +- **治理先行** — 命名规范、词根词典、元数据在建模前就定好 +- **速赢验证** — 3 个月不出第一个结果,项目大概率会死 +- **业务痛点驱动** — 先解决业务方每天骂的问题,再谈架构完整性 + +### 别犯的错 + +- **选最时髦的技术栈** — 团队没用过的技术 = 延期风险 +- **Big Bang 建设** — 一次性建满六层,上线后发现 80% 的表没人用 +- **治理后置** — "先跑通再加规范"的代价是半年后全部重构 +- **管理层不看数据** — 问清楚谁在用数据、用数据做哪些决策。没人用就别建 + +## 三个转型行业的具体建模路径 + +### 制造业 → 智慧工厂 + +系统集成顺序:PLM(产品设计)→ ERP(资源计划)→ MES(制造执行)→ IIoT(设备物联网)。 + +事实表选择: +- 生产工单 → 累积快照事实表(一条记录贯穿工单全生命周期) +- 设备运行 → 周期快照事实表(每小时一条,设备状态 + 产量) +- 质量追溯 → 事务事实表 + SCD Type 2(追溯每次质检的缺陷信息) + +**不要**把设备传感器数据全部入数仓——设备振动频率、主轴转速这些高频数据放 TSDB,数仓只存小时级聚合。 + +### 零售 → 新零售 + +系统集成顺序:POS + 电商平台 → OMS(订单管理)+ WMS(库存管理)→ 会员 OneID → CDP(客户数据平台)。 + +核心建模决策: +- 线上和线下订单用**同构事实表**——加 `channel_type` 字段区分渠道 +- 会员用 OneID 统一——微信 openid、手机号、会员卡号、邮箱全部打通 +- 库存用**半累加快照**——门店级库存每天拍一次,DC 级库存实时 + +**不要**把线上电商 GMV 和线下门店销售额混算——口径不一样(线上含退款前、线下含税),统一口径比统一表结构更难。 + +### 医疗 → 数字化医院 + +系统集成顺序:HIS(医院信息系统)→ EMR(电子病历)→ LIS(检验)+ PACS(影像)→ 互联网医院。 + +核心建模: +- 就诊事实表——一次就诊一行,关联患者、医生、科室、诊断 +- 处方用桥接表——一个处方含多种药品 +- 病历用 JSON + 关系型混合——结构化字段(诊断编码)用列存,自由文本用 JSON 存 + +**必须**在 ODS 入库时就脱敏——患者数据一旦入数仓,合规风险就已经产生。 From 26b07f2f5d326d8953c96837a8c2820eb5b6fde4 Mon Sep 17 00:00:00 2001 From: jnMetaCode Date: Tue, 9 Jun 2026 21:49:37 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=20STORED=20AS=20O?= =?UTF-8?q?RC/TBLPROPERTIES=20=E7=AD=89=20Hive=20=E7=89=B9=E5=AE=9A?= =?UTF-8?q?=E8=AF=AD=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/sql-templates.md | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/skills/data-warehouse-modeling/scripts/sql-templates.md b/skills/data-warehouse-modeling/scripts/sql-templates.md index 0dc7e12..5fd182a 100644 --- a/skills/data-warehouse-modeling/scripts/sql-templates.md +++ b/skills/data-warehouse-modeling/scripts/sql-templates.md @@ -26,11 +26,6 @@ CREATE TABLE IF NOT EXISTS ods.ods_mysql_trade_order_di ( ) COMMENT '交易域-订单表-日增量' PARTITIONED BY (ds STRING COMMENT '业务日期 yyyy-MM-dd') -STORED AS ORC -TBLPROPERTIES ( - 'orc.compress' = 'SNAPPY', - 'lifecycle' = '180' -- 保留180天 -); ``` ## 二、DWD 明细宽表模板 @@ -69,11 +64,6 @@ CREATE TABLE IF NOT EXISTS dwd.dwd_trade_order_detail_di ( ) COMMENT '交易域-订单明细-日增量' PARTITIONED BY (ds STRING COMMENT '业务日期 yyyy-MM-dd') -STORED AS ORC -TBLPROPERTIES ( - 'orc.compress' = 'SNAPPY', - 'lifecycle' = '730' -- 保留2年 -); ``` ### DWD ETL 模板(ODS → DWD) @@ -138,31 +128,6 @@ CREATE TABLE IF NOT EXISTS dim.dim_user_info_df ( ) COMMENT '用户域-用户维度表-日全量' PARTITIONED BY (ds STRING COMMENT '快照日期 yyyy-MM-dd') -STORED AS ORC -TBLPROPERTIES ('lifecycle' = '365'); -``` - -### SCD Type 2 拉链表 - -```sql --- DIM 维度表:拉链表(SCD Type 2,保留历史变更) -CREATE TABLE IF NOT EXISTS dim.dim_product_info_zipper ( - product_id BIGINT COMMENT '商品ID(业务键)', - product_sk BIGINT COMMENT '代理键', - product_name STRING COMMENT '商品名称', - category1_name STRING COMMENT '一级类目', - category2_name STRING COMMENT '二级类目', - brand_name STRING COMMENT '品牌名称', - price DECIMAL(18,2) COMMENT '价格', - status STRING COMMENT '状态', - start_dt STRING COMMENT '生效日期', - end_dt STRING COMMENT '失效日期(9999-12-31表示当前有效)', - is_current TINYINT COMMENT '是否当前版本 0否1是', - etl_time TIMESTAMP COMMENT 'ETL处理时间' -) -COMMENT '商品域-商品维度拉链表' -STORED AS ORC -TBLPROPERTIES ('lifecycle' = '9999'); ``` ### 拉链表更新 DML @@ -219,7 +184,6 @@ CREATE TABLE IF NOT EXISTS dws.dws_trade_user_1d_df ( ) COMMENT '交易域-用户粒度-近1天汇总-日全量' PARTITIONED BY (ds STRING COMMENT '统计日期 yyyy-MM-dd') -STORED AS ORC; ``` ### DWS ETL 模板 @@ -283,7 +247,6 @@ CREATE TABLE IF NOT EXISTS ads.ads_sales_daily_report ( etl_time TIMESTAMP COMMENT 'ETL处理时间' ) COMMENT '销售日报-省份类目粒度' -STORED AS ORC; -- ADS ETL INSERT OVERWRITE TABLE ads.ads_sales_daily_report