> 驾驭维度建模、Data Vault 与湖仓一体三把利剑——让数据资产"活"起来。
┌──────────────────────────────┐
│ 数据建模方法论矩阵 │
└──────────────┬───────────────┘
│
┌──────────────┬─────────────────┼──────────────┬─────────────┐
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ Kimball │ │ Inmon │ │ Data │ │ Anchor │ │ OneData │
│ 维度建模 │ │ 企业信息 │ │ Vault │ │ Modeling│ │ 阿里 │
│ 星型模型 │ │ 工厂 CIF │ │ 数据金库 │ │ 锚建模 │ │ 数据建模 │
└────┬────┘ └────┬─────┘ └────┬─────┘ └────┬────┘ └────┬─────┘
│ │ │ │ │
┌────┴──────────────┴────────────────┴─────────────┴────────────┘
│ 数据分层架构(ODS → DWD → DWS → ADS) │
└────────────────────────────────────────────────────────────────┘
┌─────────────┐
│ dim_date │
│ 日期维度 │
└──────┬──────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───┴───────┐ ┌─────┴─────┐ ┌───────┴──────┐
│dim_store │ │ fact_sales│ │ dim_product │
│ 门店维度 │─│ 销售事实 │─│ 商品维度 │
└───────────┘ └─────┬─────┘ └──────────────┘
│
┌───────┴───────┐
│ │
┌─────┴─────┐ ┌───────┴──────┐
│dim_user │ │ dim_channel │
│ 用户维度 │ │ 渠道维度 │
└───────────┘ └──────────────┘
核心原则:
1. 事实表在中心,维度表围绕事实表(星型)
2. 事实表:业务过程度量值 + 外键(数字、可加)
3. 维度表:描述性属性(文本、层次)
4. 维度退化到事实表(如订单号,直接放事实表)
┌─────────────────────────────────────────────────┐
│ Inmon CIF 架构 │
├─────────────────────────────────────────────────┤
│ │
│ 数据源 → ODS → Staging → 3NF EDW → 数据集市 │
│ (各业务 (贴源) (清洗) (企业级 (部门级 │
│ 系统) 3范式仓库) 维度建模) │
│ │
│ 关键特征: │
│ · EDW 层严格 3NF,消除冗余 │
│ · 数据集市层按部门做维度建模(星型) │
│ · 自上而下设计,先建 EDW 再建 Mart │
│ · 适合:大型企业,多部门,长期规划 │
└─────────────────────────────────────────────────┘
Kimball vs Inmon:
| 维度 | Kimball | Inmon |
|:---|:---|:---|
| **设计** | 自下而上(业务过程驱动) | 自上而下(企业全局驱动) |
| **中心** | 数据集市(星型模型) | 企业数据仓库(3NF) |
| **范式** | 维度建模(反范式) | 第三范式(规范化) |
| **速度** | 快速交付(周/月) | 长周期(月/年) |
| **耦合** | 业务过程紧耦合 | 数据松耦合 |
| **适合** | 快速迭代、敏捷团队 | 大型企业、合规要求高 |
┌─────────────────────────────────────────────────┐
│ Data Vault 2.0 三件套 │
├─────────────────────────────────────────────────┤
│ │
│ Hub(业务实体核心键): │
│ ┌─────────────────┐ │
│ │ Hub_Customer │ │
│ │ ─────────────── │ │
│ │ customer_hash_key│ ← 业务主键的哈希 │
│ │ customer_id │ ← 业务主键 │
│ │ load_date │ ← 加载时间 │
│ │ record_source │ ← 数据来源 │
│ └────────┬────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Link_Cust_Order │ ← 实体间关系 │
│ │ ─────────────── │ │
│ │ link_hash_key │ │
│ │ customer_hash_key│ │
│ │ order_hash_key │ │
│ │ load_date │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Sat_Customer │ ← 描述性属性+时间线 │
│ │ ─────────────── │ │
│ │ customer_hash_key│ │
│ │ load_date │ │
│ │ customer_name │ │
│ │ email │ │
│ │ city │ │
│ └─────────────────┘ │
│ │
│ 优势: │
│ · 并行加载:Hub/Link/Sat 可独立写入 │
│ · 可审计:每条记录带 load_date + record_source │
│ · 弹性扩展:新增数据源不破坏现有结构 │
│ · 历史追溯:Sat 自动保留全部历史版本 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ ADS (Application Data Service) 应用数据服务层 │
│ · 报表/大屏/KPI 指标 │
│ · 高度汇总,面向具体应用 │
│ · 例:每日GMV大屏、用户留存报表 │
├─────────────────────────────────────────────────────────┤
│ DWS (Data Warehouse Service) 数据仓库服务层 │
│ · 轻度汇总(主题宽表) │
│ · 面向主题域:用户主题/商品主题/交易主题 │
│ · 例:用户日活宽表、商品SKU日汇总表 │
├─────────────────────────────────────────────────────────┤
│ DWD (Data Warehouse Detail) 数据仓库明细层 │
│ · 清洗后的明细数据 │
│ · 维度退化、统一编码、去重去噪 │
│ · 例:订单事实表、用户行为日志明细 │
├─────────────────────────────────────────────────────────┤
│ ODS (Operational Data Store) 操作数据存储层 │
│ · 贴源层,原始数据不做处理 │
│ · 增量/全量同步,保留原始结构 │
│ · 例:MySQL binlog 同步、API 日志原始入湖 │
└─────────────────────────────────────────────────────────┘
-- =====================================================
-- DWD 层:订单明细事实表(维度建模)
-- 来源:ODS 层业务库多表 JOIN 清洗
-- =====================================================
CREATE TABLE dwd.fact_order_detail (
-- 退化维度(直接放事实表)
order_id BIGINT COMMENT '订单ID(退化维度)',
order_no VARCHAR(32) COMMENT '订单编号',
-- 维度外键
user_key BIGINT COMMENT '用户维度键(代理键)',
product_key BIGINT COMMENT '商品维度键',
store_key BIGINT COMMENT '门店维度键',
date_key INT COMMENT '日期维度键 YYYYMMDD',
channel_key INT COMMENT '渠道维度键',
-- 度量值(事实)
order_amount DECIMAL(18,2) COMMENT '订单金额',
discount_amount DECIMAL(18,2) COMMENT '优惠金额',
actual_amount DECIMAL(18,2) COMMENT '实付金额',
product_quantity INT COMMENT '商品数量',
product_count INT COMMENT 'SKU数',
-- 可加事实(可跨维度聚合)
is_first_order TINYINT COMMENT '是否首单 1-是 0-否',
is_coupon_used TINYINT COMMENT '是否用券',
-- 审计字段
etl_time TIMESTAMP COMMENT 'ETL处理时间',
source_table VARCHAR(64) COMMENT '来源表'
)
PARTITION BY (date_key)
COMMENT '订单明细事实表(DWD层)';
-- DWS 层:用户主题日汇总宽表
CREATE TABLE dws.dws_user_daily (
user_key BIGINT,
date_key INT,
-- 用户维度属性(退化到宽表)
user_level VARCHAR(16) COMMENT '用户等级',
city VARCHAR(32),
register_date DATE,
-- 交易指标
order_count INT COMMENT '订单数',
order_amount DECIMAL(18,2) COMMENT '订单金额',
actual_amount DECIMAL(18,2) COMMENT '实付金额',
avg_order_amount DECIMAL(18,2) COMMENT '客单价',
first_order_count INT COMMENT '首单数',
-- 行为指标
login_count INT COMMENT '登录次数',
browse_duration INT COMMENT '浏览时长(秒)',
add_cart_count INT COMMENT '加购次数',
-- 衍生指标
pay_conversion_rate DECIMAL(5,4) COMMENT '支付转化率',
retention_day1 DECIMAL(5,4) COMMENT '次日留存率',
etl_time TIMESTAMP
)
PARTITION BY (date_key)
COMMENT '用户主题日汇总宽表(DWS层)';
-- ADS 层:流量转化漏斗
CREATE TABLE ads.ads_conversion_funnel_daily (
date_key INT,
step_name VARCHAR(32) COMMENT '漏斗步骤: 曝光/点击/加购/下单/支付',
user_count BIGINT COMMENT '用户数',
step_rate DECIMAL(5,4) COMMENT '步骤转化率',
overall_rate DECIMAL(5,4) COMMENT '整体转化率'
)
COMMENT '流量转化漏斗(ADS层)';
┌────────────────────────────────────────────────────────┐
│ 缓慢变化维(SCD)策略 │
├────────┬─────────────┬─────────────────────────────────┤
│ Type │ 策略 │ 示例 │
├────────┼─────────────┼─────────────────────────────────┤
│ Type 0 │ 保留原始值 │ 出生日期(永不变化) │
│ Type 1 │ 直接覆盖 │ 用户昵称(旧值不重要) │
│ Type 2 │ 新增行 │ 用户等级升级(需要历史) │
│ Type 3 │ 新增列 │ 部门更名(只保留前一个值) │
│ Type 4 │ 历史表分离 │ 用户地址(主表+历史表) │
│ Type 6 │ 混合(1+2+3) │ 自选属性(部分覆盖+部分新增行) │
└────────┴─────────────┴─────────────────────────────────┘
-- =====================================================
-- SCD Type 2:用户维度表(拉链表)
-- 每次属性变更 → 插入新行,旧行标记过期
-- =====================================================
-- 维度表结构
CREATE TABLE dim.dim_user (
user_key BIGINT COMMENT '代理键(自增,唯一)',
user_id VARCHAR(32) COMMENT '业务主键',
user_name VARCHAR(64),
user_level VARCHAR(16) COMMENT '普通/白银/黄金/钻石',
city VARCHAR(32),
phone VARCHAR(20),
-- SCD Type 2 核心字段
effective_date DATE COMMENT '生效日期',
expire_date DATE COMMENT '失效日期(9999-12-31=当前有效)',
is_current TINYINT COMMENT '是否当前版本 1-是 0-否',
etl_time TIMESTAMP
);
-- 处理逻辑(每日 ETL)
-- 1. 新用户 → 直接插入
INSERT INTO dim.dim_user (user_id, user_name, user_level, city, phone,
effective_date, expire_date, is_current)
SELECT
user_id, user_name, user_level, city, phone,
CURRENT_DATE, '9999-12-31', 1
FROM ods.user
WHERE user_id NOT IN (SELECT user_id FROM dim.dim_user WHERE is_current = 1);
-- 2. 属性变更 → 关闭旧版本 + 插入新版本
-- Step 2a: 关闭过期版本
UPDATE dim.dim_user
SET expire_date = CURRENT_DATE - 1,
is_current = 0
WHERE user_id IN (
SELECT user_id FROM ods.user
WHERE CONCAT(user_name, user_level, city, phone)
!= (SELECT CONCAT(user_name, user_level, city, phone)
FROM dim.dim_user d
WHERE d.user_id = ods.user.user_id AND d.is_current = 1)
)
AND is_current = 1;
-- Step 2b: 插入新版本
INSERT INTO dim.dim_user (user_id, user_name, user_level, city, phone,
effective_date, expire_date, is_current)
SELECT
u.user_id, u.user_name, u.user_level, u.city, u.phone,
CURRENT_DATE, '9999-12-31', 1
FROM ods.user u
JOIN dim.dim_user d ON u.user_id = d.user_id AND d.is_current = 1
WHERE CONCAT(u.user_name, u.user_level, u.city, u.phone)
!= CONCAT(d.user_name, d.user_level, d.city, d.phone);
-- 查询技巧:
-- 查当前用户 → is_current = 1
-- 查历史快照 → 事实表 date_key BETWEEN effective_date AND expire_date
SELECT f.*, d.user_level
FROM dwd.fact_order_detail f
JOIN dim.dim_user d
ON f.user_key = d.user_key
AND f.date_key BETWEEN d.effective_date AND d.expire_date;
┌──────────────────┬─────────────────────┬──────────────────┐
│ 事实表类型 │ 粒度 │ 示例 │
├──────────────────┼─────────────────────┼──────────────────┤
│ 事务事实表 │ 一行=一次事务 │ 订单表、支付流水 │
│ Transaction │ 最细粒度 │ │
├──────────────────┼─────────────────────┼──────────────────┤
│ 周期快照事实表 │ 一行=一个周期状态 │ 库存日快照 │
│ Periodic Snapshot│ 定期采样 │ 账户余额日表 │
├──────────────────┼─────────────────────┼──────────────────┤
│ 累积快照事实表 │ 一行=实体全生命周期 │ 订单生命周期表 │
│ Accumulating │ 多状态日期列 │ 保险理赔全程表 │
├──────────────────┼─────────────────────┼──────────────────┤
│ 无事实事实表 │ 多对多关系 │ 学生选课事实表 │
│ Factless │ 只有外键无度量 │ 参会记录表 │
└──────────────────┴─────────────────────┴──────────────────┘
-- 一行记录跟踪订单的完整生命周期
CREATE TABLE dwd.fact_order_lifecycle (
order_id BIGINT,
user_key BIGINT,
product_key BIGINT,
date_key INT,
-- 多状态日期列
order_create_time TIMESTAMP COMMENT '下单时间',
order_pay_time TIMESTAMP COMMENT '支付时间',
order_ship_time TIMESTAMP COMMENT '发货时间',
order_deliver_time TIMESTAMP COMMENT '签收时间',
order_cancel_time TIMESTAMP COMMENT '取消时间',
-- 时效度量
pay_duration INT COMMENT '支付耗时(分钟)',
ship_duration INT COMMENT '发货耗时(小时)',
delivery_duration INT COMMENT '配送耗时(小时)',
is_cancelled TINYINT COMMENT '是否取消',
-- 金额
order_amount DECIMAL(18,2),
actual_amount DECIMAL(18,2),
etl_time TIMESTAMP,
update_time TIMESTAMP COMMENT '最后更新时间'
);
-- ETL: 每天用最新状态 UPDATE 对应列,而非 INSERT 新行
原子指标(不可拆分):
支付金额、订单数、用户数、GMV
派生指标(原子指标 + 维度 + 时间):
最近7天 各品类 支付金额
本月 新用户 订单数
复合指标(派生指标运算):
客单价 = 支付金额 / 订单数
转化率 = 支付用户数 / 访问用户数
同比增长 = (本期 - 同期) / 同期
-- dbt 模型:指标标准化定义
-- models/metrics/user_metrics.yml
metrics:
- name: dau
label: 日活跃用户数
description: 当日至少打开一次APP的去重用户数
type: simple
type_params:
measure: count_distinct_user
filter: |
{{ dimension('event_type') }} = 'app_launch'
time_grains: [day, week, month]
- name: gmv
label: 总交易额
description: 所有支付成功的订单金额总和
type: simple
type_params:
measure: sum_order_amount
filter: |
{{ dimension('order_status') }} = 'paid'
- name: conversion_rate
label: 下单转化率
description: 访问用户中完成下单的用户占比
type: ratio
type_params:
numerator: count_distinct_order_users
denominator: count_distinct_visit_users
# models/
# ├── staging/ # 基础层:ODS → Staging(重命名+类型转换)
# ├── intermediate/ # 中间层:Staging → 业务实体
# └── marts/ # 集市层:中间层 → 维度表/事实表
# staging/stg_orders.sql
SELECT
id AS order_id,
user_id,
CAST(amount AS DECIMAL(18,2)) AS amount,
status,
created_at AS order_create_time,
_ingested_at AS etl_time
FROM {{ source('raw', 'orders') }}
# marts/dim_user.sql
WITH user_versions AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC) AS rn
FROM {{ ref('stg_users') }}
)
SELECT
{{ dbt_utils.generate_surrogate_key(['user_id']) }} AS user_key,
user_id,
user_name,
user_level,
city,
CURRENT_DATE AS effective_date,
'9999-12-31' AS expire_date,
TRUE AS is_current
FROM user_versions
WHERE rn = 1
# marts/fact_sales_daily.sql
{{
config(
materialized='incremental',
partition_by={'field': 'date_key', 'data_type': 'int'},
unique_key=['date_key', 'user_key', 'product_key']
)
}}
SELECT
DATE_FORMAT(order_create_time, '%Y%m%d') AS date_key,
{{ dbt_utils.generate_surrogate_key(['user_id']) }} AS user_key,
{{ dbt_utils.generate_surrogate_key(['product_id']) }} AS product_key,
COUNT(DISTINCT order_id) AS order_count,
SUM(amount) AS total_amount,
COUNT(DISTINCT user_id) AS user_count
FROM {{ ref('stg_orders') }}
WHERE status = 'paid'
{% if is_incremental() %}
AND order_create_time >= (SELECT MAX(etl_time) FROM {{ this }})
{% endif %}
GROUP BY date_key, user_key, product_key
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| :--- | :--- | :--- |
| 大宽表几百列 | 更新锁、存储膨胀、维护难 | DWS 层做主题拆分 |
| 用业务ID做关联 | 字符串 JOIN 极慢 | 统一用代理键(数字) |
| 维度属性塞事实表 | 事实表膨胀、更新困难 | 事实表只放外键+度量 |
| 拉链表不用代理键 | 无法区分历史版本 | user_id + user_key 分离 |
| ODS 直接出报表 | 数据脏、计算重复 | 必经 DWD→DWS→ADS |
| 多层 CROSS JOIN | 笛卡尔积爆炸 | 先聚合再 JOIN |
| 分区键不用在 WHERE | 全表扫描 | WHERE 必须含分区键 |
数据量 < 100GB + 团队 < 5人
→ Kimball 维度建模 + dbt + 单机 MySQL/PostgreSQL
数据量 100GB~10TB + 多业务线
→ Kimball + Inmon 混合(DWD星型 + EDW 3NF)+ dbt + ClickHouse/Doris
数据源频繁变化 + 需要完整审计
→ Data Vault 2.0 + dbt + Snowflake/BigQuery
超大规模 + 实时 + 半结构化
→ 湖仓一体(Iceberg/Hudi) + dbt + Trino/Spark
超大企业 + 严格合规
→ Inmon CIF + Data Vault(ODS 层)+ Teradata/Snowflake
□ 业务过程矩阵(业务过程 × 维度)
□ 总线矩阵(共享维度一致性)
□ 概念数据模型(CDM)
□ 逻辑数据模型(LDM):实体关系图
□ 物理数据模型(PDM):DDL + 分区/索引策略
□ ETL 映射文档(源表 → 目标表字段映射)
□ 数据字典(表/字段/枚举值说明)
□ 数据血缘图(dbt docs + lineage)
□ 指标口径文档(原子指标→派生指标→复合指标)
□ SCD 策略清单(每个维度的变更策略)
*(内容由AI生成,仅供参考)*
共 1 个版本