大数据

dwd层建模思考03

问题:我有个思路为什么不能添加dwt层主题层,在主题层中,将dws层轻度聚合的数据进行拼接。比如dwt层company表 字段有:专利数、软著数、联系人数、联系方式数、不同类型联系方式数等。这些字段都来自dws层。在ads层 只需要结合dwd层和dwt层 针对不同的需求,组合dwt层不同的字段指标即可。类似拼装积木的效果。如果这样做就变成了我现在的,ods层变dwd层只对删除数据过滤、对字典码值关联名称,数据清洗、标准化、退化维度,其他不做。dws轻度聚合,dwt汇聚dws聚合结果,ads层根据业务需求关联dwd层明细以及dwt指标。

✅ 一、你的思路总结(DWT 层设计)

你提出的架构:

ODS →(仅过滤删除+码值)→ DWD  
DWD →(轻度聚合)→ DWS  
DWS →(横向拼接指标)→ DWT(主题宽表)  
ADS →(按需组合 DWD + DWT)→ 报表/API

举例:
dwt_company_theme 表包含:

  • patent_count ← 来自 dws_company_patent_stat
  • copyright_count ← 来自 dws_company_copyright_stat
  • valid_contact_count ← 来自 dws_company_contact_effectiveness
  • wechat_channel_count ← 来自 dws_company_channel_type_dist
    ...

ADS 层只需:

SELECT 
  dwd.company_name,
  dwt.patent_count,
  dwt.valid_contact_count,
  ...
FROM dwd_company_base dwd
JOIN dwt_company_theme dwt ON dwd.company_id = dwt.company_id

✅ 二、这个思路的优点(为什么有吸引力)

优点说明
🧩灵活组装ADS 层像搭积木,自由组合不同主题指标,响应业务需求快
🔁复用性强DWT 作为“指标中间层”,避免 ADS 层重复 JOIN 多个 DWS 表
📊宽表友好适合 BI 工具(如 Tableau、QuickBI)直接拖拽字段,降低使用门槛
🧱职责分离DWS 负责“垂直聚合”,DWT 负责“横向拼接”,逻辑清晰

💡 本质上,你设计的 DWT 层 = 指标宽表(Metric Wide Table)或 逻辑数据集市(Logical Data Mart)

⚠️ 三、潜在问题 & 风险(为什么经典架构不推荐)

1. ❌ DWT 层容易变成“上帝表”(God Table)

  • 一个表包含几十甚至上百个指标 → 字段爆炸
  • 维护困难:新增一个指标,要改 DWT 表结构 + 重跑历史数据
  • 查询性能下降:即使只查2个字段,也要扫描整张宽表

📌 举例:
未来新增“近30天专利增长率”、“联系人响应率”等指标,DWT 表不断膨胀,最终变成“谁都不敢动的巨无霸”。

2. ❌ DWT 依赖多个 DWS,数据一致性难保障

  • DWS_A 今日更新,DWS_B 昨日更新 → DWT 拼接后数据口径不一致
  • 调度依赖复杂:DWT 必须等所有依赖的 DWS 都 ready 才能跑
  • 出问题难排查:某个指标异常,要逐层回溯到源头 DWS

3. ❌ 过度依赖 DWT,丧失灵活性

  • 业务想分析“专利类型 × 联系人职位”的交叉分布 → DWT 没这个组合,只能回 DWS 或 DWD 重算
  • DWT 是“预设组合”,无法应对“临时、探索性分析”

4. ❌ DWD 层过于简单,失去“建模”价值

你提到:

DWD 层只对删除数据过滤、对字典码值关联名称,数据清洗、标准化、退化维度,其他不做”

→ 这会导致:

  • DWD 表字段过多(90个)、未筛选、未重构 → 下游 JOIN 性能差
  • 未做业务建模(如识别“企业资质评估”、“客户触达”等过程)→ 无法支撑灵活分析
  • 本质上,你把“建模”的责任推给了 DWS/DWT,导致上游混乱

📌 DWD 层的核心价值是“面向分析主题的原子建模”,不是“ODS 美化版”。

✅ 四、如何优化你的思路?—— 融合经典架构 + 你的创新

我们不否定你的“DWT 积木思路”,而是把它放在正确的位置,用正确的方式实现。

🎯 方案:保留 DWT 层,但重新定义其角色 + 优化 DWD/DWS

✅ Step 1: 强化 DWD 层(必须做!)
筛选字段:90 → 40~50 核心字段
业务建模:按“企业资质”、“客户触达”等过程拆分 DWD 表
退化维度:企业名称、行业、地区等高频维度
清洗标准化:统一单位、码值、空值处理

💡 DWD 是地基,地基不牢,DWT 再漂亮也会塌。

✅ Step 2: DWS 层做轻度聚合(保持不变)

  • 按主题、按维度、按时间聚合
  • 如:dws_company_patent_by_type, dws_contact_channel_dist

✅ Step 3: DWT 层改为“可选宽表”或“物化视图”(关键优化!)

  • 不作为必经分层,而是按需构建的“加速层”
  • 只为高频、固定报表服务(如 CEO 看板、销售日报)
  • 使用 Doris 物化视图(MV)或 Rollup Table 实现,自动同步,无需手动维护

📌 示例:

-- 创建物化视图(自动同步,查询自动路由)
CREATE MATERIALIZED VIEW mv_company_dashboard AS
SELECT
  dwd.company_id,
  dwd.company_name,
  dws1.patent_count,
  dws2.copyright_count,
  dws3.valid_contact_count,
  dws4.wechat_count
FROM dwd_company_base dwd
LEFT JOIN dws_company_patent_stat dws1 ON dwd.company_id = dws1.company_id
LEFT JOIN dws_company_copyright_stat dws2 ON dwd.company_id = dws2.company_id
LEFT JOIN dws_company_contact_effectiveness dws3 ON dwd.company_id = dws3.company_id
LEFT JOIN dws_company_channel_type_dist dws4 ON dwd.company_id = dws4.company_id
WHERE dwd.data_date = '2025-09-22'
  AND dws1.data_date = '2025-09-22'
  ...;

✅ 优势:

  • ADS 层查询 mv_company_dashboard 像查普通表,性能极佳
  • Doris 自动维护 MV,数据一致性有保障
  • 不影响原始 DWD/DWS 结构,灵活性仍在

✅ Step 4: ADS 层自由选择数据源

  • 简单固定报表 → 查 DWT(物化视图)
  • 复杂临时分析 → 查 DWD + DWS 自由组合
  • 探索性分析 → 直接查 DWD 原子表

✅ 五、最终推荐架构(融合版)

ODS(贴源)
  ↓ 清洗、标准化、建模、退化维度(必须做!)
DWD_company_base_inc          → 企业基础(原子粒度)
DWD_contact_channel_inc       → 联系方式(原子粒度,Array标签)
  ↓ 按主题聚合
DWS_company_patent_stat       → 专利统计
DWS_company_contact_stat      → 联系统计
DWS_company_channel_dist      → 渠道分布
  ↓ (可选)物化视图 / 宽表(按需构建)
DWT_company_dashboard_mv      → 高频指标宽表(物化视图)
  ↓ 应用层(自由选择)
ADS → 
  - 固定报表:SELECT * FROM DWT_company_dashboard_mv
  - 临时分析:JOIN DWD + DWS
  - 探索分析:直接查 DWD

✅ 六、为什么这个方案更好?

你的原方案优化后方案
DWD 过于简单 → 下游性能差、难维护✅ DWD 强化建模 → 地基牢固
DWT 是必经层 → 易膨胀、难维护✅ DWT 是可选物化视图 → 按需构建,自动维护
依赖链长 → 调度复杂✅ DWS 独立更新,MV 自动同步 → 解耦
丧失灵活性✅ ADS 可自由选择数据源 → 兼顾灵活与性能

🧭 总结:你的“积木思路”很有价值,只需稍作调整
可以有 DWT 层,但应该是“物化宽表”或“加速视图”,不是核心分层
DWD 层必须做强,不能只是“ODS 美化版”
用 Doris 物化视图实现 DWT,自动同步、查询透明、性能极佳
ADS 层自由选择:简单查 DWT,复杂查 DWD+DWS

这样,你既能享受“积木式组装”的灵活性,又能保证系统长期可维护、高性能、易扩展。

问题:但是这样存在一个问题,由于dwt都是组装好的,这样使用的频率肯定很高,这样慢慢的也会大量的ads去查dwt,这样又回到了我之前dwt必经的线路了

✅ 一、为什么“DWT 被滥用”是必然?

🚀性能好、查询简单SELECT * FROM dwt_companyJOIN 5张表快10倍,谁不爱?
👶使用门槛低业务/BI/新人直接拖拽字段,无需理解底层模型
📈初期ROI极高1张宽表解决80%报表,老板觉得“数仓真牛”
🔄路径依赖一旦用上,没人愿意改回复杂查询,即使需求变了

结果:DWT 从“加速视图”沦为“唯一入口”,架构僵化,扩展性归零。

✅ 二、如何破解?—— 架构治理 + 技术约束 + 组织协同

我们不阻止“用 DWT”,而是引导它用在正确的地方,同时保留灵活出口。以下是经过验证的解决方案:

🛠️ 方案一:【架构治理】明确 DWT 的“服务边界”
📌 DWT 只服务“高频、稳定、核心”指标,其他需求必须走 DWD/DWS

如何落地?

  1. 制定《DWT 宽表准入规范》
    • 只允许 TOP 10 高频指标入 DWT(如专利总数、有效联系人)
    • 新增指标必须评审:是否高频?是否稳定?是否跨部门通用?
    • 禁止“临时需求”、“探索性分析”直接入 DWT
  2. 建立“宽表生命周期管理”
    • 每季度 Review:哪些字段没人用?哪些需求已过时?
    • 对低频字段归档或移除,防止膨胀
  3. 文档化“替代方案”
    • 在 DWT 表注释中写明:“如需按专利类型分析,请查询 dws_company_patent_by_type
    • 提供 SQL 模板,引导用户走正确路径

🛠️ 方案二:【技术约束】用“视图”代替“物理表”,强制解耦

📌 不建物理 DWT 表,而是创建“逻辑视图(View)”,底层仍指向 DWS

示例:

-- 创建逻辑视图(非物化)
CREATE VIEW dwt_company_theme AS
SELECT
  dwd.company_id,
  dwd.company_name,
  dws1.patent_count,
  dws2.copyright_count,
  dws3.valid_contact_count
FROM dwd_company_base dwd
LEFT JOIN dws_company_patent_stat dws1 ON dwd.company_id = dws1.company_id AND dws1.data_date = '2025-09-22'
LEFT JOIN dws_company_copyright_stat dws2 ON dwd.company_id = dws2.company_id AND dws2.data_date = '2025-09-22'
LEFT JOIN dws_company_contact_effectiveness dws3 ON dwd.company_id = dws3.company_id AND dws3.data_date = '2025-09-22'
WHERE dwd.data_date = '2025-09-22';

优势:

优势说明
无物理存储不占空间,无维护成本
强制走 DWS查询视图 = 查询底层 DWS,口径永远一致
可随时重构改底层 DWS 不影响视图接口
⚠️ 性能略低每次查询都实时 JOIN,适合中小数据量或 Doris MPP 优化

💡 如果性能不够,再对高频视图按需创建物化视图(见方案三)

🛠️ 方案三:【技术升级】用“动态物化视图”或“查询路由”

📌 利用 Doris 的“智能物化视图”或“查询改写”能力,自动选择最优路径

Doris 2.0+ 方案:

  1. 创建多个细粒度物化视图(非大宽表)
-- 物化视图1:专利相关
CREATE MATERIALIZED VIEW mv_company_patent AS
SELECT company_id, patent_count, invention_count
FROM dws_company_patent_stat;

-- 物化视图2:联系人相关
CREATE MATERIALIZED VIEW mv_company_contact AS
SELECT company_id, valid_contact_count, wechat_count
FROM dws_company_contact_effectiveness;

创建逻辑宽表视图

CREATE VIEW dwt_company_theme AS
SELECT 
  dwd.company_id,
  dwd.company_name,
  mv1.patent_count,
  mv2.valid_contact_count
FROM dwd_company_base dwd
LEFT JOIN mv_company_patent mv1 ON dwd.company_id = mv1.company_id
LEFT JOIN mv_company_contact mv2 ON dwd.company_id = mv2.company_id;
  1. Doris 自动查询路由
    • 当用户查 dwt_company_theme 时,Doris 自动识别哪些字段有物化视图,优先走 MV
    • 无 MV 的字段,走原始 DWS
    • 用户无感知,性能最优,架构不僵化

✅ 这才是“积木式”的终极形态:

  • 用户看到的是“宽表”
  • 底层是“独立物化积木”
  • 系统自动组装最优查询路径

🛠️ 方案四:【组织协同】建立“数据产品”思维

📌 把 DWT 当成“数据产品”,不是“技术表”

如何做?

  1. 为 DWT 定义 SLA 和 Owner
    • 谁负责维护?谁审批新增字段?谁监控性能?
    • 像管理一个“API 产品”一样管理 DWT
  2. 提供“数据目录 + 使用指南”
    • 哪些字段适合什么场景?
    • 哪些需求应该走其他路径?
    • 示例 SQL、性能预期、更新频率
  3. 培训 + 考核
    • 培训业务方“何时用 DWT,何时用 DWS”
    • 考核数仓团队“宽表膨胀率”、“物化视图覆盖率”

✅ 五、终极架构推荐(防滥用版)

ODS
  ↓ 强化建模、清洗、退化
DWD(原子事实 + 退化维度)
  ↓ 按主题聚合
DWS(轻度聚合,独立更新)
  ↓ (可选)细粒度物化视图(非宽表)
MV_patent, MV_contact, MV_channel...
  ↓ 逻辑宽表视图(自动路由)
DWT_company_theme (VIEW)  ← 用户主要入口
  ↓ 应用层
ADS → 
  - 90% 场景:查 DWT_company_theme(自动走 MV,性能好)
  - 10% 复杂场景:直接查 DWS 或 DWD(灵活)

📌 关键控制点:

  • DWT 是 VIEW,不是 TABLE
  • 底层是 多个独立 MV,不是单一大宽表
  • Doris 自动查询改写,用户无感知
  • 团队有 治理规范,防止无序扩张

🧭 总结:你不是在和架构斗争,而是在和“人性”与“惯性”斗争

你的担忧完全正确 —— “好用的东西会被滥用”是铁律

破解之道不是“禁止使用”,而是“引导 + 约束 + 自动化”

  1. 用“逻辑视图 + 细粒度物化”代替“物理宽表” → 防止膨胀
  2. 用 Doris 查询改写实现“无感知最优路径” → 兼顾性能与灵活
  3. 建立治理规范和数据产品思维 → 控制无序增长

这样,你既能享受“积木式组装”的敏捷,又能避免“上帝表”的噩梦。

问题:我的理解是dwt可以看作 企业画像、联系人画像、这里只记录画像该有的东西,没有的还是要从dwd或者dws出

“DWT 可以看作企业画像、联系人画像,这里只记录画像该有的东西,没有的还是要从 DWD 或 DWS 出

✅ 一、为什么这个理解是“黄金标准”?

1. 🖼️ DWT = 画像层 = 数据产品层

  • 不是“大杂烩宽表”,而是围绕特定主体(企业、联系人)的“特征快照”
  • 只包含稳定、高频、通用、预计算的画像标签/指标
  • 目标是:“一眼看清这个企业/联系人长什么样”

举例:

  • 企业画像:是否高新专利总数行业竞争力等级联系人活跃度
  • 联系人画像:是否关键人主联系方式有效性最近联系时间意向等级

2. 🚫 “没有的,从 DWD/DWS 出” —— 保持灵活性

  • 画像层不追求“大而全”,而是“准而快”
  • 深度分析、临时需求、交叉下钻 → 回到原子层(DWD)或聚合层(DWS)
  • 避免把 DWT 变成“万能表”,而是“入口表”

📌 这和互联网大厂的“用户画像系统”设计完全一致:

  • Profile 层:性别、年龄、RFM 分层、LTV 预估(稳定特征)
  • 临时分析:行为序列、交叉漏斗、AB 实验 → 走明细日志或聚合表

✅ 二、DWT 画像层设计原则(防滥用指南)

原则说明示例
只放“画像特征”,不放“过程明细”画像是“结果”,不是“过程”✔️专利总数
每条专利的申请号
只放“稳定指标”,不放“临时指标”避免频繁变更表结构✔️企业规模等级
昨日新增专利数(放DWS)
只放“通用标签”,不放“业务专属”避免烟囱化✔️是否有效联系人
销售A的私有打标
明确“更新频率”和“口径”避免误用注释:每日凌晨更新,基于前一日快照
提供“下钻路径”文档引导深度分析注释:如需按专利类型分析,请查 dws_company_patent_by_type

✅ 三、推荐表结构示例(DWT 画像层)

📌 dwt_company_profile —— 企业画像宽表

company_id BIGINT COMMENT '企业ID',
company_name VARCHAR(200) COMMENT '企业名称',
industry_category VARCHAR(50) COMMENT '行业分类',
province_name VARCHAR(50) COMMENT '注册省份',
company_scale_level TINYINT COMMENT '企业规模等级(1~5)',
is_high_tech BOOLEAN COMMENT '是否高新技术企业',
total_patent_count INT COMMENT '有效专利总数',
total_copyright_count INT COMMENT '软件著作权总数',
contact_valid_rate FLOAT COMMENT '有效联系方式占比',
last_contact_days_ago INT COMMENT '距最近联系天数',
customer_value_score FLOAT COMMENT '客户价值评分(RFM模型)',
profile_update_time DATETIME COMMENT '画像更新时间',
-- 分区
data_date DATE COMMENT '画像快照日期'

💡 说明:

  • 所有字段都是预计算、稳定、高频使用的画像特征
  • 不包含“专利类型分布”、“联系人明细”等过程数据
  • 每个字段都有注释,说明来源和更新逻辑

📌 dwt_contact_profile —— 联系人画像宽表

contact_id BIGINT,
company_id BIGINT,
contact_name VARCHAR(100),
position_name VARCHAR(50),
is_key_person BOOLEAN COMMENT '是否关键决策人',
main_channel_type VARCHAR(20) COMMENT '主联系方式类型',
main_channel_valid BOOLEAN COMMENT '主联系方式是否有效',
last_contact_time DATETIME COMMENT '最近联系时间',
contact_frequency_30d INT COMMENT '近30天联系次数',
intent_level TINYINT COMMENT '意向等级(1~5)',
tag_list ARRAY<VARCHAR(50)> COMMENT '标签列表:["技术背景","高意向"]',
profile_update_time DATETIME,
data_date DATE

💡 说明:

  • tag_list 用 Array 存储,便于 Doris 的 ARRAY_CONTAINS 查询
  • 不存储“每个标签的打标时间”(那是 DWD 的事)
  • intent_level 是模型计算结果,非原始打标

✅ 四、如何与 DWD/DWS 协同?

🔄 数据流向:

ODS →(清洗建模)→ DWD(原子事实)  
DWD →(聚合)→ DWS(轻度汇总)  
DWS + DWD →(特征工程)→ DWT(画像宽表)  
ADS →  
  ├─ 简单画像查询 → DWT  
  └─ 复杂分析 → JOIN DWD + DWS

🎯 查询示例:
场景1:销售看“高意向企业清单” → 查 DWT

SELECT 
  company_name,
  total_patent_count,
  contact_valid_rate,
  customer_value_score
FROM dwt_company_profile
WHERE customer_value_score > 80
  AND data_date = '2025-09-22';

场景2:分析“发明专利 vs 联系人职位的关系” → 查 DWS + DWD

SELECT 
  dwd.position_name,
  dws.patent_type,
  COUNT(*) as cnt
FROM dwd_contact_base dwd
JOIN dws_company_patent_by_type dws ON dwd.company_id = dws.company_id
WHERE dws.data_date = '2025-09-22'
GROUP BY dwd.position_name, dws.patent_type;

✅ 完美分工:

  • DWT:“是什么”(What)—— 快速获取主体特征
  • DWD/DWS:“为什么”(Why)—— 深度归因分析

✅ 五、技术实现建议(Doris)

使用 物化视图 自动构建画像

CREATE MATERIALIZED VIEW dwt_company_profile
DISTRIBUTED BY HASH(company_id)
AS
SELECT
  dwd.company_id,
  dwd.company_name,
  dwd.industry_category,
  dwd.province_name,
  -- 从 DWS 获取聚合指标
  dws1.total_patent_count,
  dws2.contact_valid_rate,
  -- 计算画像标签
  CASE 
    WHEN dws1.total_patent_count > 10 THEN 5
    WHEN dws1.total_patent_count > 5 THEN 4
    ELSE 3
  END AS company_scale_level,
  ...
FROM dwd_company_base dwd
LEFT JOIN dws_company_ipr_stat dws1 ON dwd.company_id = dws1.company_id
LEFT JOIN dws_company_contact_effectiveness dws2 ON dwd.company_id = dws2.company_id
WHERE dwd.data_date = '2025-09-22'
  AND dws1.data_date = '2025-09-22';

对 tag_list 字段建 倒排索引

CREATE INDEX idx_tag_list ON dwt_contact_profile (tag_list) USING INVERTED;

→ 加速 SELECT * WHERE ARRAY_CONTAINS(tag_list, '高意向')

分区 + 生命周期

PARTITION BY RANGE(data_date) (
  PARTITION p202509 VALUES LESS THAN ('2025-10-01'),
  ...
)
-- 保留最近365天

🧭 总结:你已掌握数仓分层设计的“道”与“术”

DWT = 画像层 = 数据产品层
只放稳定、高频、通用的画像特征
深度分析回 DWD/DWS,保持灵活性
用物化视图 + 倒排索引实现高性能
用文档和规范防止滥用

问题:还有一个问题 dwd层在存储联系方式来源使用数组,那么存储的是来源的id还是退化后来源的名字

✅ 在 DWD 层存储联系方式来源(或标签)时,使用 Array 类型,应该存储 来源ID(code),而不是退化后的名字(name)!

✅ 一、为什么存 ID(code)而不是 name?

原因说明
🔄维度属性可能变更来源名称可能修改(如 “爬虫” → “网络采集”),如果存 name,历史数据语义会错乱
📏存储更小、性能更好ID 通常是 INT 或 SHORT VARCHAR,比名称字符串更省空间,JOIN/过滤更快
🧩便于后续灵活映射下游可根据不同场景映射不同名称(如中文/英文/缩写),或做国际化
🧭符合维度建模规范Kimball 维度建模中,事实表存外键(ID),维度表存描述(name)
🛠️ETL 更健壮如果来源名称拼错或未映射,存 ID 仍可追溯,存 name 则直接脏数据

📌 举例:

  • 来源表:source_id=1, source_name='爬虫'
  • 今天存了 ['爬虫'] → 明天来源表改成 source_name='网络采集'
  • 历史数据仍显示“爬虫”,但新数据是“网络采集” → 口径不一致,报表对不齐!

而如果存 ID:

  • ['1']
  • 无论名称怎么改,1 始终代表同一个来源
  • 下游展示时再 JOIN 维度表 → 语义永远一致

✅ 二、Doris 中的最佳实践(Array + ID + 运行时映射)

DWD 表结构(存 ID)

-- dwd_contact_channel_inc
CREATE TABLE dwd_contact_channel_inc (
  channel_id BIGINT,
  contact_id BIGINT,
  company_id BIGINT,
  channel_type VARCHAR(20),
  channel_value VARCHAR(100),
  source_codes ARRAY<VARCHAR(10)>,   -- 存来源ID,如 ['1', '3']
  tag_codes ARRAY<VARCHAR(20)>,      -- 存标签ID,如 ['valid', 'not_company']
  is_valid BOOLEAN AS ARRAY_CONTAINS(tag_codes, 'valid'), -- 用ID判断
  ...
) ENGINE=OLAP
PARTITION BY RANGE(data_date) (...)
DISTRIBUTED BY HASH(channel_id);

维度表(存 name)

-- dim_source(来源维度表)
CREATE TABLE dim_source (
  source_code VARCHAR(10) COMMENT '来源ID',
  source_name VARCHAR(50) COMMENT '来源名称',
  source_category VARCHAR(20) COMMENT '来源分类',
  is_active BOOLEAN,
  etl_time DATETIME
) ENGINE=OLAP
DUPLICATE KEY(source_code);

查询时动态映射名称(推荐两种方式)
方式1:LATERAL VIEW + JOIN(灵活,适合分析)

-- 查询时展开并映射名称
SELECT 
  c.channel_id,
  c.channel_value,
  s.source_name,
  t.tag_name
FROM dwd_contact_channel_inc c
LATERAL VIEW EXPLODE(c.source_codes) tmp1 AS source_code
LATERAL VIEW EXPLODE(c.tag_codes) tmp2 AS tag_code
JOIN dim_source s ON tmp1.source_code = s.source_code
JOIN dim_tag t ON tmp2.tag_code = t.tag_code
WHERE c.data_date = '2025-09-22';

方式2:创建视图封装(简化下游使用)

-- 创建带名称的视图
CREATE VIEW dwd_contact_channel_with_name AS
SELECT 
  c.*,
  -- 用 ARRAY_MAP + JOIN 映射名称(Doris 2.1+)
  ARRAY_MAP(x -> (SELECT source_name FROM dim_source WHERE source_code = x), c.source_codes) AS source_names,
  ARRAY_MAP(x -> (SELECT tag_name FROM dim_tag WHERE tag_code = x), c.tag_codes) AS tag_names
FROM dwd_contact_channel_inc c;

⚠️ 注意:ARRAY_MAP + 子查询 在 Doris 中可能性能不佳,建议用方式1或物化视图。

方式3:在 DWS/ADS 层做映射(推荐用于报表)

-- 在聚合层或应用层做最终映射
SELECT 
  s.source_name,
  COUNT(*) as cnt
FROM dwd_contact_channel_inc c
LATERAL VIEW EXPLODE(c.source_codes) tmp AS source_code
JOIN dim_source s ON tmp.source_code = s.source_code
GROUP BY s.source_name;

✅ 三、什么情况下可以存 name?

极少情况,仅当:

  1. 维度极小且永不变更(如性别:'M'/'F',但即使这样也建议存 code)
  2. 无维度表,且 name 是业务主键(如某些系统直接用 name 当唯一标识)
  3. 临时分析,不入生产数仓

🚫 在企业级数仓中,强烈不建议在 DWD 存 name

✅ 四、标签(tag)同理!

标签也应存 tag_code,而不是 tag_name

-- 正确
tag_codes: ['valid', 'not_company', 'high_intent']

-- 错误(未来名称变更会导致历史数据语义混乱)
tag_names: ['有效', '非本公司', '高意向']

维度表:

-- dim_tag
tag_code    | tag_name     | tag_category
'valid'     | '有效'       | '有效性'
'not_company'| '非本公司'   | '归属'
'high_intent'| '高意向'     | '意向等级'

✅ 五、性能优化(Doris)

source_codes, tag_codes 建倒排索引

CREATE INDEX idx_source_codes ON dwd_contact_channel_inc (source_codes) USING INVERTED;
CREATE INDEX idx_tag_codes ON dwd_contact_channel_inc (tag_codes) USING INVERTED;

→ 加速 ARRAY_CONTAINS(source_codes, '1') 查询

维度表用 DUPLICATE KEY 或 PRIMARY KEY 模型
→ 加速 JOIN

在 DWS 层预聚合 + 映射名称

-- dws_channel_source_dist
CREATE MATERIALIZED VIEW dws_channel_source_dist AS
SELECT 
  s.source_name,
  COUNT(*) as channel_count
FROM dwd_contact_channel_inc c
LATERAL VIEW EXPLODE(c.source_codes) tmp AS source_code
JOIN dim_source s ON tmp.source_code = s.source_code
GROUP BY s.source_name;

🧭 总结:DWD 层 Array 字段必须存 ID(code)!

存 ID(code):保证语义稳定、存储高效、映射灵活
❌ 存 name:埋下数据口径不一致的定时炸弹