文章
数仓建模落地实施流程方案
适用对象:数据开发、数仓工程师、数据产品经理
技术栈:Doris + DolphinScheduler + CDC 实时入仓(10s)
约束条件:
- 无历史分区,只维护当前最新全量
- 每小时调度,串行执行(内存有限)
- 所有查询/导出必须走 ADS 层
- Doris 支持 Array,但 ADS 层必须拍平
🧭 总体流程图(5 大阶段)
[阶段1] 准备工作
↓
[阶段2] DWD 层建模(7步法)
↓
[阶段3] DWS/DWT 层构建(按需聚合+画像)
↓
[阶段4] ADS 层开发(一个需求一张表)
↓
[阶段5] 上线 & 监控 & 治理
📌 阶段1:准备工作(建模前必做)
✅ 输入
- 业务需求文档(如“销售要导出高潜力企业”)
- ODS 表清单(如
ods_crm_company_full
90 字段) - 数据字典(源系统字段含义)
- Doris 集群信息(128G/32核)
✅ 输出
- 《业务域划分清单》
- 《ODS 字段清单.xlsx》
- 《团队建模规范.md》
🛠 操作步骤
- 拉通业务方:明确 3 个核心分析主题(例:企业资质评估、客户触达、资源匹配)
- 划分业务域(模板见下)
- 整理 ODS 字段:导出所有字段名、类型、示例值、业务含义
- 制定命名规范(直接套用):
层级 | 命名规则 | 示例 |
---|---|---|
ODS | realtime_ods.ods_<库别名>_<表名>_full | ods_crm_company_full |
DWD | realtime_dwd.dwd_<业务域>_<实体>_full | dwd_company_base_full |
DWS | realtime_dws.dws_<主题>_<维度> | dws_company_summary |
DWT | realtime_dwt.dwt_<主体>_profile | dwt_company_profile |
ADS | realtime_ads.ads_<场景>_<主体>_<用途> | ads_sales_company_list |
📝 交付物模板
# 业务域划分清单
## 1. 企业资质评估域
- 包含:企业基础、专利、软著
- 不包含:联系人、联系方式
## 2. 客户触达域
- 包含:联系人、联系方式、来源、标签
- 不包含:企业资质字段
## 3. 画像与标签域
- 包含:已领取、高潜力、客户等级
- 由 DWD/DWS 推导,不来自 ODS
⚠ 避坑指南
- ❌ 不要跳过业务对齐 → 否则建完没人用
- ❌ 不要试图“一个表打天下” → 按域拆分
🧱 阶段2:DWD 层建模(7 步法)
目标:从 ODS 90 字段 → 输出 5 张干净、扁平、退化维度的 DWD 表
🔹 第1步:字段筛选(90 → 50)
操作:用 四象限法 + 业务确认
高频使用 | 低频使用 | |
---|---|---|
高质量 | ✅ 保留(核心) | ⚠ 保留(备用) |
低质量 | ⚠ 清洗后保留 | ❌ 删除 |
示例:
- ✅ 保留:
company_name
,credit_code
,province
- ⚠ 清洗:
phone
→ 标准化为 11 位 - ❌ 删除:
operator_id
,temp_flag
交付物:DWD字段筛选清单.xlsx
(业务签字确认)
🔹 第2步:清洗 & 标准化
操作:逐字段制定规则
字段 | 问题 | 清洗规则 |
---|---|---|
credit_code | 格式不一 | CASE WHEN LENGTH=18 THEN code ELSE '' END |
registered_capital | 单位混乱 | / 10000 → 万元 |
province | 原始值 | dict_map('province', raw) → 110000 |
交付物:清洗规则表.xlsx
+ dim_province
维度表
🔹 第3步:维度退化(只退化高频稳定维度)
操作:
province_code
→ 同时冗余province_name
industry_code
→ 同时冗余industry_name
- ❌ 不退化:
评分
、状态
(易变)
交付物:DWD 表结构(见下)
🔹 第4步:设计 DWD 表(扁平化 + Array 存 ID)
推荐 5 张表(按业务域拆分):
表名 | 粒度 | 关键字段 | 说明 |
---|---|---|---|
dwd_company_base_full | 企业 | company_id ,company_name ,province_code ,province_name | 退化维度 |
dwd_contact_base_full | 联系人 | contact_id ,company_id ,company_name ,position_name | 退化企业名 |
dwd_contact_channel_full | 联系方式 | channel_id ,company_id ,company_name ,source_codes ARRAY<VARCHAR> ,tag_codes ARRAY<VARCHAR> ,is_valid | Array 存 ID,预计算 is_valid |
dwd_patent_full | 专利 | patent_id ,company_id ,company_name ,patent_type_name | 退化企业名 |
dwd_copyright_full | 软著 | 类似专利 |
✅ Array 存 ID(如 ['1','3']
),不存 name!
✅ 预计算 is_valid = ARRAY_CONTAINS(tag_codes, 'valid')
🔹 第5步:编写 ETL(任务合并!)
目标:200+ 任务 → 5 个核心任务
任务名 | 处理表 | 说明 |
---|---|---|
load_dwd_company | dwd_company_base_full | 单表 |
load_dwd_contact_and_channel | dwd_contact_base_full +dwd_contact_channel_full | 一个任务写两张表 |
load_dwd_ipr | dwd_patent_full +dwd_copyright_full | 一个任务写两张表 |
ETL 示例(联系方式):
-- 步骤1:写联系人表(退化企业维度)
INSERT INTO dwd_contact_base_full
SELECT c.contact_id, c.company_id, comp.company_name, ...
FROM ods_crm_contact_full c
JOIN ods_crm_company_full comp ON c.company_id = comp.company_id;
-- 步骤2:写联系方式表(退化企业+联系人维度 + Array标签)
INSERT INTO dwd_contact_channel_full
SELECT ch.channel_id, ch.contact_id, c.company_id, c.company_name, c.contact_name,
[s.source_code] AS source_codes,
COLLECT_SET(t.tag_code) AS tag_codes,
MAX(CASE WHEN t.tag_code='valid' THEN 1 ELSE 0 END) AS is_valid
FROM ods_crm_contact_channel_full ch
JOIN dwd_contact_base_full c ON ch.contact_id = c.contact_id
LEFT JOIN ... -- 标签关联
GROUP BY ...;
🔹 第6步:测试用例(必须覆盖)
用例 | 输入 | 预期 |
---|---|---|
信用代码校验 | credit_code='123' | unified_social_credit_code='' |
标签计算 | tag_codes=['valid','not_company'] | is_valid=1 |
退化维度 | company_id=1001 | company_name='百度' |
🔹 第7步:文档 & 元数据
- 字段级血缘(来源、口径、负责人)
- 表级文档(更新频率、下游依赖)
📊 阶段3:DWS & DWT 层构建
DWS 层(轻度聚合)
- 只做必要聚合:总数、覆盖率、最大值
- 表示例:
dws_company_summary
(含total_patent_count
,valid_contact_rate
) - ETL:直接 SELECT DWD 预计算字段,无 JOIN
DWT 层(画像宽表)
- 只放稳定特征:
is_claimed
,company_grade
,customer_value_score
- ETL:JOIN DWD + DWS,主键关联,无 GROUP BY
- 示例:
INSERT INTO dwt_company_profile
SELECT dwd.company_id, dwd.company_name,
dws.total_patent_count,
CASE WHEN dws.total_patent_count > 5 THEN 'A' ELSE 'B' END AS company_grade
FROM dwd_company_base_full dwd
JOIN dws_company_summary dws ON dwd.company_id = dws.company_id;
✅ DWT 可依赖 DWT(如 dwt_company_tag
→ dwt_company_grade
),但需文档注明
📈 阶段4:ADS 层开发(唯一出口!)
原则
- 一个需求 = 一张 ADS 表
- 必须拍平 Array、格式化字段
- 禁止直接查 DWD/DWS/DWT
示例:销售企业清单
CREATE TABLE ads_sales_company_list (
company_name STRING,
industry_name STRING,
total_patent_count BIGINT,
company_grade STRING, -- 'A类客户'
export_time DATETIME
);
INSERT INTO ads_sales_company_list
SELECT dwd.company_name, dwd.industry_name,
dws.total_patent_count,
CASE WHEN dwt.customer_value_score > 90 THEN 'A类客户' ... END,
NOW()
FROM dwd_company_base_full dwd
JOIN dws_company_summary dws ON ...
JOIN dwt_company_profile dwt ON ...;
✅ source_codes ARRAY
→ ADS 层用 ARRAY_TO_STRING
转为字符串
✅ is_valid=1
→ ADS 层转为 '有效'
🚀 阶段5:上线 & 监控
上线 Checklist
- 表结构创建
- ETL 任务配置(DolphinScheduler)
- 测试数据验证
- 文档归档
- 通知下游
监控指标
指标 | 阈值 | 告警方式 |
---|---|---|
数据量波动 | > ±30% | 企业微信 |
空值率 | > 5% | 邮件 |
任务耗时 | > 20 分钟 | 短信 |
✅ 总结:这套方案让新人也能建好仓
- 有流程:5 阶段、7 步法,步步清晰
- 有模板:命名、建表、ETL、测试,直接套用
- 有避坑:Array 存 ID、ADS 拍平、任务合并
- 有治理:监控、文档、权限、变更管理
记住口诀:
DWD 建模要扁平,Array 存 ID 别存名;
DWS 聚合要轻量,DWT 画像要稳定;
ADS 出口唯一门,新人照做不出错!