热线电话:13121318867

登录
首页大数据时代别把数据仓库建成人看的地狱:Kimball 维度建模硬核指南
别把数据仓库建成人看的地狱:Kimball 维度建模硬核指南
2026-10-05
收藏

Kimball 是方法,星型模型是它产出的形状。 很多人把"Kimball vs 星型模型"当成一道选择题——这本身就是个误会:Kimball 是动词,星型模型是名词。你要做的,是遵循方法,然后得到一个落到仓库里的星型模型。

本文按顺序走一遍四步设计法,再讲那些真正需要你拍板的部分:粒度、一致性维度与总线矩阵、日期维度、缓慢变化维度(SCD),以及"雪花模型什么时候才配得上"这种罕见情形。


一、先给个痛快答案

维度建模(dimensional modeling) 是 Ralph Kimball 提出的、围绕人提问的方式来设计分析表的方法:数值度量放进事实表(fact table),用来筛选和分组的描述性上下文放进维度表(dimension table),二者用**代理键(surrogate key)**连接。

星型模型就是这套方法产出的形状——一张事实表被若干反规范化的维度环绕。

  • 雪花模型(snowflake):同一个星型,把维度规范化成子表,省存储、费连接。在列式数仓上通常不值得。
  • Inmon(CIF):真正意义上的另一个方法——先建规范化的企业级数仓,往下游再切维度化数据集市。"Kimball vs Inmon"才是真命题。

真正要你决定的是三件事:度量在维度间是可加、半可加还是不可加;哪些维度必须一致性(conformed),好让多张事实表能相互比较;以及每个维度如何处理历史——Type 1 覆盖、Type 2 加行+有效期、Type 3 加"上一值"列。

术语 它实际指什么
维度建模 技术本身:把度量与上下文分开,度量进事实、上下文进维度,用代理键连接
Kimball 方法 技术周围的生命周期:四步设计法、总线矩阵、一致性维度、逐个业务过程建仓
星型模型 产出:一张事实表直接连接反规范化的维度表
雪花模型 同一个星型把维度规范化成子表——一种物理变体,不是另一种方法
Inmon(CIF) 真正不同的方法:先规范化企业数仓,再往下游建维度化集市

二、什么是维度建模(在 Kimball 方法里)

维度建模是一种优先考虑查询简洁与性能、而非存储效率的数据仓库设计技术。由 Ralph Kimball 在 1990 年代提出、写入 The Data Warehouse Toolkit,至今仍是分析领域的默认做法,因为它按业务用户思考数据的方式去建模。在现代 Lakehouse 里,它就是你的 Gold 层:位于清洗后的 Silver 表下游(medallion 架构)。

为什么不能直接用 3NF?

第三范式(3NF)对 OLTP 极好:最小化冗余、防止更新异常。但它对分析极差:查询要 15+ 次 join、性能崩塌,分析师不把你的 schema 读到博士级别就看不懂这个模型。

核心原则:把"发生了什么"与"上下文"分开

  • 事实表:存事件与度量——下了单、被点击、付了款。含可聚合的度量(sum/count/avg)。
  • 维度表:存描述性上下文——谁(客户)、什么(产品)、何时(日期)、何地(地点)。用于筛选与分组。

Kimball 的哲学:"数据仓库的好坏,只取决于它所支撑的商业智能。" 维度模型是为人设计的,其次才是为机器。 如果分析师不求助就写不出查询,这个模型就是失败的。

”

三、事实表 vs 维度表

这是每个星型模型的两块砖。这个切分搞对了,后面一切顺理成章。

事实表

  • 记录业务事件/交易
  • 含数值度量(金额、数量、时长)
  • 有指向维度表的外键
  • 又高又窄(行多列少);粒度 = 一行一个事件

维度表

  • 存描述性属性
  • 含用于筛选/分组的文本字段
  • 主键是代理键
  • 又矮又宽(行少列多);粒度 = 一行一个实体
问题 若答案是…… 它是……
能对它 SUM / COUNT / AVG 吗? 能 事实
会按它 GROUP BY 或筛选吗? 会 维度
它在描述一个实体吗? 是 维度
它在记录一个事件/交易吗? 是 事实
-- 事实表:一行一个订单行项目
CREATE TABLE fct_order_lines (
    order_line_sk       BIGINT PRIMARY KEY,    -- 代理键
    order_id            VARCHAR(50),           -- 自然键(退化维度)
    customer_sk         BIGINT REFERENCES dim_customers,
    product_sk          BIGINT REFERENCES dim_products,
    date_sk             INT REFERENCES dim_date,
    -- 度量(可聚合)
    quantity            INT,
    unit_price          DECIMAL(10,2),
    discount_amount     DECIMAL(10,2),
    line_total          DECIMAL(10,2)
);

-- 维度表:一行一个客户
CREATE TABLE dim_customers (
    customer_sk         BIGINT PRIMARY KEY,    -- 代理键
    customer_id         VARCHAR(50),           -- 自然键
    customer_name       VARCHAR(255),
    email               VARCHAR(255),
    segment             VARCHAR(50),           -- 'Enterprise', 'SMB', 'Consumer'
    acquisition_channel VARCHAR(50),
    created_at          TIMESTAMP
);

可加 / 半可加 / 不可加度量

事实表里不是每个数都能在所有维度上求和。Kimball 把度量分成三类,这个区分能拦住一张看板报出一个没人能复现的数字:

类型 可对哪些维度求和 例子
可加(Additive) 所有维度 line_total、quantity
半可加(Semi-additive) 部分维度,但绝不含日期 account_balance、inventory_on_hand(跨店求和,跨天取均值或末值)
不可加(Non-additive) 任何维度都不行 margin_percent、conversion_rate。存分子与分母作为可加事实,聚合后再算比值

四、怎么设计星型模型:Kimball 的四步

四步按固定顺序,顺序很重要:每一步都在收窄下一步。

  1. 选一个业务过程。 一个源系统记录的过程:接单、发货、开票、处理工单。不要选部门("市场部"),不要选报表("周度收入 deck")。围着部门建模,产出的表只能回答那个部门当下的问题;围着过程建模,产出的表每个部门都能复用。
  2. 声明粒度。 写一句话:"一行对应一个产品在一个销售交易行"。在列任何一列之前先声明它,并选源能给出的最低原子粒度——因为聚合可以从原子推导,原子无法从聚合还原。之后加的每一列,都必须在这个粒度上为真——这就是让事实表保持诚实的检验。
  3. 识别维度。 问:在这个粒度上,什么在描述这个事件——谁、什么、何时、何地、如何。零售行项目就是:日期、门店、产品、客户、促销、收银员。每个都变成事实表里的一个外键。
  4. 识别度量。 过程产出的数值度量:数量、单价、折扣额、行总额。尽量保持可加;存分量而不是比值,好让比值能在聚合后重算。任何你手痒想放进去的文本,都是维度属性或退化维度。

"混合粒度"这个最贵的错误

维度建模里最贵的错误,是把订单级的值(如运费、整单折扣)放进行级事实表。每一份对它求和的报表,都会把它乘以订单的行数。要么把该值分摊到行,要么另建一张订单粒度的事实表,靠"跨表钻取"来对比两者。

代理键是"第 0 步"

每个维度都拿一个由仓库生成的、无意义的整数主键;事实表存这个键,而不是源系统的标识符。这才让一个客户能在 SCD Type 2 下存在成多行、让源系统迁移后重新编号 id 时依然存活、并让连接永远落在定宽整数上。把自然键作为属性存在旁边。

四步走一遍的例子

步骤 零售销售 SaaS 订阅
业务过程 顾客在收银台购买 为一个订阅周期开具发票
粒度 一行一个交易行上的一个产品 一行一个计费周期上的一条发票行
维度 日期、门店、产品、客户、促销、收银员 日期、账户、套餐、币种、销售代表
度量 数量、单价、折扣额、行总额 席位数、单价、折扣额、开票额

五、星型 vs 雪花

雪花模型,就是把维度规范化。原本一张 dim_product 带着 category 和 department 两列,现在变成 dim_product → dim_category → dim_department。事实本身没变,变的是分析师要写的 join 数、和引擎要跑的 join 数。

  • 星型(首选):维度反规范化、join 更少、查询更快、更易懂,代价是轻微冗余(分析场景可接受)。
  • 雪花:维度规范化、join 更多、查询更慢、没有文档就难查,换来省存储(今天几乎不重要)。
-- 星型:按部门看收入,一次 join
SELECT p.department, SUM(f.line_total)
FROM fct_order_lines f
JOIN dim_products p ON f.product_sk = p.product_sk
GROUP BY p.department;

-- 雪花:同一份报表,三次 join
SELECT dp.department_name, SUM(f.line_total)
FROM fct_order_lines f
JOIN dim_products    p  ON f.product_sk  = p.product_sk
JOIN dim_categories  c  ON p.category_sk = c.category_sk
JOIN dim_departments dp ON c.department_sk = dp.department_sk
GROUP BY dp.department_name;

"省存储"这个论据,基本经不起算术。 一个 30 万产品、30 亿订单行的零售商,每 1 万行事实才对应约 1 行维度——把 category 文本从产品维度里规范化出去,省下的是整个仓库存储的千分之几。在列式引擎上省得更少,因为字典编码早就把重复的 category 字符串每块只存一次了。而你为此加的每一次 join,都是分析师每跑一次查询都要付的成本。

什么时候才雪花化:只有当维度真的是层级结构、且分析师频繁在不同层级上独立查询时(如 产品 → 类别 → 部门)。此外两种情况通常被接受:外挂维度(outrigger)——一个维度引用另一个小维度(客户维度指向日期维度以记录开户日),把日历属性集中在一处、而不是复制二十列;以及从 MDM 系统进来就已经规范化的维度,有时保持原样、通过一个扁平视图暴露给分析师。


六、维度的种类

不是所有维度都一样。理解这些模式,能让你从第一天就建对。

  1. 一致性维度(Conformed):被多张事实表共用。dim_date、dim_customer 被 fct_orders、fct_page_views、fct_support_tickets 共用。企业一致性的关键。
  2. 角色扮演维度(Role-Playing):同一维度被多次使用、含义不同。dim_date 作为 order_date、ship_date、delivery_date 各 join 一次。建视图或别名。
  3. 退化维度(Degenerate):存在事实表里、却没有独立维度表的维度键。订单号、发票号、交易号——没有值得单独存的属性。
  4. 杂项维度(Junk):把零散的低基数标志位合成一张维度。与其在事实表里放 5 个布尔列,不如建一张 dim_order_flags 包含所有组合。
  5. 日期维度(Date):最重要的维度。预计算属性:day_of_week、is_weekend、fiscal_quarter、holiday_flag。始终用整数代理键(YYYYMMDD 格式)。
  6. 外挂维度(Outrigger):被另一个维度内部引用的维度,如 dim_customer 持有开户日的日期键。这是 Kimball 唯一无争议接受的"雪花"。 谨慎使用,绝不用于维度的主层级。
  7. 桥接表(Bridge,多对多):当一行事实真的关联多个维度成员时(一次就诊多个诊断、一本书多个作者、一个账户多个持有人)。桥接表夹在事实与维度之间,带一个权重因子,让度量能被分摊而不是被重复计数。

七、一致性维度与 Kimball 总线矩阵

一个维度是一致性(conformed)的,当多张事实表使用同一张维度表,或使用键与属性取值含义完全相同的维度表。这才是把一堆分散的星型变成一个仓库的东西:如果 fct_orders 和 fct_support_tickets 都 join dim_customer,那么"企业客户细分"在两侧指的就是同一批客户,两个过程就能相互比较。一致性维度,是 Kimball 对"为什么两个团队给同一个词报出两个数"这个杀死大多数仓库的问题的回答。

总线矩阵:把整个仓库规划在一页纸上

在任何一张表存在之前,先画一个网格:行是业务过程(每个对应一张事实表),列是维度。在每个"过程用到某维度"的格子上打勾。被勾多次的列,就是必须一致化的维度,也是要最先建的维度——因为其他一切都依赖它们。行则给出了交付顺序:交付一个星型,再下一个,每个都复用已存在的维度。

业务过程(事实表) 日期 客户 产品 门店 促销
接单 X X X X X
发货 X X X
退货 X X X
处理工单 X X X
库存快照 X X X

读完这张矩阵,建设顺序自己就写出来了:日期、客户、产品几乎被每个过程使用,所以它们是一致性维度、最先建,且有单一归属、单一含义;门店和促销用得少,可以等。

多事实星型:跨表钻取,绝不事实连事实

一个仓库里有若干共享一致性维度的事实表,有时被称为事实星座(fact constellation)或星系模型。这很正常、也是预期之中的。随之而来的规则是绝对的:绝不把两张事实表直接相连。 它们粒度不同,连接会放大行数、让两侧所有度量都膨胀。Kimball 的技术叫跨表钻取(drilling across):分别查询每个星型,把两个结果都按同一组一致性维度属性分组,再把两份汇总后的结果集按这些属性 join。

-- 错误:连接两张事实表会放大行数
-- SELECT SUM(o.line_total), SUM(r.refund_amount)
-- FROM fct_order_lines o JOIN fct_returns r ON o.product_sk = r.product_sk

-- 正确:先各自聚合,再按一致性属性连接
WITH orders AS (
    SELECT d.year_num, d.month_num, p.category,
           SUM(f.line_total) AS revenue
    FROM fct_order_lines f
    JOIN dim_date     d ON f.date_sk    = d.date_sk
    JOIN dim_products p ON f.product_sk = p.product_sk
    GROUP BY 1, 2, 3
),
returns AS (
    SELECT d.year_num, d.month_num, p.category,
           SUM(f.refund_amount) AS refunds
    FROM fct_returns f
    JOIN dim_date     d ON f.date_sk    = d.date_sk
    JOIN dim_products p ON f.product_sk = p.product_sk
    GROUP BY 1, 2, 3
)
SELECT COALESCE(o.year_num, r.year_num)   AS year_num,
       COALESCE(o.month_num, r.month_num) AS month_num,
       COALESCE(o.category, r.category)   AS category,
       COALESCE(o.revenue, 0)             AS revenue,
       COALESCE(r.refunds, 0)             AS refunds
FROM orders o
FULL OUTER JOIN returns r
  ON  o.year_num  = r.year_num
  AND o.month_num = r.month_num
  AND o.category  = r.category;

八、Kimball 日期维度

每个维度模型都有它,而它也是人们最常建错的维度。重点不是存日期(事实表已经有日期键了),重点是存下报表可能想分组或筛选、而 SQL 又无法从日期本身推导的一切:不按日历走的财季、公司假日、交易日、以及报表所用语言里的星期名。

  • YYYYMMDD 形式的整数键:20260819 能正确排序、在调试裸事实表时可读、分区裁剪也友好。这是 Kimball 唯一允许代理键"携带含义"的地方。
  • 一天一行,只加载一次:从最早事实之前,一直排到未来数年。二十年约 7300 行——生成一次、偶尔扩展,永远不要在计算表达式上 join。
  • 保留特殊行,绝不留空键:加"日期未知""尚未发生"两行,用保留的负键。累积快照事实需要它们——发货日在下单时是空的,而空外键会破坏连接。
  • 时刻另放:把分钟放进 dim_date,会让行数乘以 1440。另建 dim_time_of_day,或在无人按时段分组时,直接在事实上存一个时间戳。

日期维度也是经典的角色扮演维度:一张订单事实会以 order_date、ship_date、delivery_date 三次四次地 join 它。建一张物理表、按角色各暴露一个视图,让每个角色在 BI 工具里能带自己的列名。

CREATE TABLE dim_date (
    date_sk             INT PRIMARY KEY,       -- YYYYMMDD 格式
    date_actual         DATE NOT NULL,
    day_of_week         VARCHAR(10),           -- 'Monday', ...
    day_of_week_num     INT,                   -- 1-7
    day_of_month        INT,
    day_of_year         INT,
    week_of_year        INT,
    month_num           INT,
    month_name          VARCHAR(10),
    quarter_num         INT,
    quarter_name        VARCHAR(10),           -- 'Q1', 'Q2', ...
    year_num            INT,
    fiscal_year         INT,
    fiscal_quarter      INT,
    is_weekend          BOOLEAN,
    is_holiday          BOOLEAN,
    holiday_name        VARCHAR(50)
);

-- 用法:轻松按任意日期属性筛选/分组
SELECT d.month_name, d.year_num, SUM(f.order_total) as revenue
FROM fct_orders f
JOIN dim_date d ON f.order_date_sk = d.date_sk
WHERE d.is_weekend = FALSE
GROUP BY d.month_name, d.year_num;

九、缓慢变化维度(SCD Type 0 到 6)

维度属性会随时间变化:客户搬家、产品被重分类、员工调部门。怎么处理这些变化,对历史准确性至关重要。 Kimball 把选项编了号,好让团队按属性、而不是按表来达成一致。

  • Type 0:固定。 永不改变,原值永久保留。用于本就不该变的属性(原始注册日、出生日期)。
  • Type 1:覆盖。 用新值替换旧值,不留历史。用于纠错、以及历史不重要的属性。代价:你再也答不出"这个客户下这个订单时是什么细分"。
  • Type 2:加新行(最常用)。 用新的代理键建一行新维度,用 effective_date、expiry_date、is_current 标记。保留完整历史。适合任何"历史准确性重要"的属性:细分变化、地址变化、价格档变化。
  • Type 3:加新列。 加 current_value、previous_value 两列,历史有限(通常只有一个前值)。罕见:只在恰好需要"前后对比"时有用。Type 2 几乎总是更好。
  • Type 4:迷你维度。 把快速变化的属性拆进一张独立小维度、单独建键。用于否则 Type 2 维度行数会爆炸的场景。例:把客户人口统计分箱进 dim_customer_profile(年龄段、收入段、信用段),dim_customer 保留稳定属性。
  • Type 6:混合(1+2+3)。 一组 Type 2 行,同时带一个"当前值"列、在每一版上被覆盖。这样报表既能按"事件当时的取值"分组,也能按"今天的取值"分组。
-- SCD Type 2:客户细分从 'SMB' 变为 'Enterprise'
-- 之前:1 行
customer_sk | customer_id | segment    | effective_date | expiry_date | is_current
1           | C001        | SMB        | 2023-01-01     | 9999-12-31  | TRUE

-- 之后:2 行(旧行失效,新增一行)
customer_sk | customer_id | segment    | effective_date | expiry_date | is_current
1           | C001        | SMB        | 2023-01-01     | 2024-06-15  | FALSE
2           | C001        | Enterprise | 2024-06-15     | 9999-12-31  | TRUE

-- 历史查询:客户下单时是什么细分?
SELECT o.order_id, c.segment as segment_at_order_time
FROM fct_orders o
JOIN dim_customers c ON o.customer_sk = c.customer_sk
-- 事实表里的 customer_sk 指向正确的历史版本

Type 2 的坑:用 Type 2 时,你必须在加载时决定"给事实表分配哪个代理键"。通常你要的是事件发生时"当期"的那个版本——这需要在 ETL 里做一次"时间点查找(point-in-time lookup)"。

”

十、事实表的种类

不同的业务过程,要求不同的事实表设计。按你事件的性质来选。

  • 事务事实(Transaction):一行一个事件、最低粒度。最常见。订单、点击、支付、登录。粒度:一行一个订单行项目。
  • 周期快照事实(Periodic Snapshot):一行一个时间段,按规律间隔捕捉状态。账户余额、库存水位、管道快照。度量是半可加的:跨账户可求和,跨天不可求和。 粒度:一行一个账户一天。
  • 累积快照事实(Accumulating Snapshot):一行一个过程实例,随里程碑推进而被更新。订单履约、贷款申请、工单。是唯一一种"插入之后还会被回访"的事实表。 粒度:一行一个订单(贯穿生命周期更新)。
  • 无度量事实(Factless Fact):只记事件、没有度量,只有外键。学生出勤、产品促销覆盖。用于回答"哪些学生上了课""哪些产品在促销"。
CREATE TABLE fct_order_fulfillment (
    order_sk                BIGINT PRIMARY KEY,
    order_id                VARCHAR(50),
    customer_sk             BIGINT,
    -- 多个日期外键(里程碑)
    order_date_sk           INT,
    payment_date_sk         INT,
    ship_date_sk            INT,
    delivery_date_sk        INT,
    -- 滞后度量(算出来的)
    days_to_payment         INT,
    days_to_ship            INT,
    days_to_delivery        INT,
    -- 度量
    order_total             DECIMAL(10,2),
    current_status          VARCHAR(50)
);

-- 行随订单在履约流程中推进而被更新
-- 初始:只填充 order_date_sk
-- 付款后:填 payment_date_sk,算 days_to_payment
-- 发货后:填 ship_date_sk,算 days_to_ship
-- 送达后:填 delivery_date_sk,算 days_to_delivery

十一、星型模型 vs 一张大宽表(OBT)

对 Kimball 的现代质疑是:列式数仓已经抽掉了星型存在的理由——既然引擎只读查询碰到的列,为什么不把事实和所有维度属性拍平进一张大宽表、省掉 join?这就是 OBT(one big table) 模式,对单个看板确实更快。但取舍的关键不是速度,而是第二、第三个看板来的时候会发生什么。

关注点 星型模型 一张大宽表
查询形状 每个维度一次 join,各引擎都优化得很好 无 join,对某个已知报表最快
改一个属性 更新一行维度,所有事实立即看到 重写宽表里带这个值的每一行
历史 显式,按属性通过 SCD 类型管理 在构建时被"焊死"进行里,事后难改
跨过程比较 一致性维度,跨表钻取 每张表各自重新定义同一属性,直到彼此矛盾
新问题来了 通常能用已有表回答 常常要从头新建一张宽表

可行的立场不是二选一:把星型作为记录基准(model of record)——粒度在这里声明、维度在这里一致化、历史在这里被处理一次;再在它下游物化宽表,一个看板一张、或一个 BI 数据集一张,把它们当成缓存:派生的、可丢弃的、从星型重建。这样宽表拿到它的速度,星型守住定义的诚实。


十二、最佳实践清单

  • 先画总线矩阵:业务过程为行、维度为列,在写任何 DDL 之前。
  • 先定义粒度:设计任何事实表前,显式写出粒度("一行一个订单行""一行一个客户一天")。绝不混粒度。
  • 加载最低原子粒度:聚合可从原子推导,原子无法从聚合还原。汇总表是从原子事实之后派生,而不是取代它。
  • 用代理键:所有维度表用整数代理键,自然键存为属性。这才能正确处理 SCD Type 2、并改善 join 性能。
  • 建一致性维度:dim_date、dim_customer 应跨所有事实表共享,同样的键、同样的属性。
  • 绝不事实连事实:不同粒度的两张事实表会互相放大。先各自聚合到一致性属性,再连接结果——这就是跨表钻取。
  • 反规范化维度:优先星型而非雪花。把 category、department、region 直接放进维度表。存储便宜,join 昂贵。
  • 加一个日期维度:永不在原始日期上 join。建 dim_date 带预计算属性,用整数键(YYYYMMDD)以便分区裁剪。
  • 用默认行处理空值:在维度里建"Unknown""Not Applicable"行(SK = -1)。事实表里绝不留空外键。
  • 把粒度写进文档:每张事实表都应记录它的粒度。这能防止意外重复计数,帮分析师写对查询。
  • 重要属性用 SCD Type 2:客户细分、产品类别、员工部门——任何会变、且会影响分析的东西,都该是 Type 2。

"分析师测试":设计完模型后,让一个分析师不看文档写 5 个常见查询。如果每个都能在 5 分钟内写完,模型就是好的;如果他们需要提问或犯错,就简化你的设计。

”

十三、常见问题(FAQ)

Kimball 和星型模型是一回事吗? 不是二选一。Kimball 是设计方法(也叫维度建模):选业务过程、声明粒度、选维度、选事实。星型模型是该方法产出的表布局。真正的比较是 Kimball vs Inmon——后者的方法先建规范化企业数仓。

星型 vs 雪花? 星型的维度反规范化、直接连中心事实表,呈星形。雪花把维度规范化成子维度(产品→类别→部门)。分析场景优先星型,因为更易查、性能更好:一个分组报表每个维度只需一次 join,而不是每层层级一次。

Kimball 推荐雪花吗? 一般不。雪花化会增加 join、让模型更难导航、几乎不省东西——因为相比事实表,维度在仓库里只占极小的行数份额。列式引擎用字典编码压缩重复文本,冗余的成本比多出来的 join 更低。外挂维度和真正庞大且真层级的维度,是被许可的例外。

什么是总线矩阵? 在任何表存在之前画的网格。行是业务过程(各自成为一张事实表),列是维度。每个"过程用到某维度"的格子打勾。被勾的列就是必须一致化的维度,行给出实施顺序,一次交付一个星型。

日期维度里该放什么? 一个日历日一行,YYYYMMDD 形式的整数代理键,外加报表可能分组/筛选的每个属性:星期名、周序号、月名、季度、年、财年与财季、周末标志、假日标志与假日名。几千行覆盖数十年。时刻放进独立维度,以免行数倍增。

该用代理键还是自然键? 维度表主键用代理键(自增整数),自然键(customer_id 这类业务标识)存为属性。代理键稳定、join 性能好,而且正是它让 SCD Type 2 得以工作——因为同一个自然键需要多行。自然键会变,会导致静默的连接失败。

一张大宽表比星型更好吗? 在列式引擎上,OBT 对单个看板可能更快,因为列裁剪省掉了 join。但你在别处付出代价:改一个属性要重写整张表、历史被焊进行里、同一个客户定义被复制进每张宽表直到副本彼此矛盾。把星型作为记录基准,在它下游物化宽表。


写在最后

维度建模,是数据工程里最接近"永不褪色的技能"的东西。工具从 Hadoop 换到 Spark、从本地换到云、从手写 SQL 换到 dbt——但"把数据组织成事实与维度"这门纪律,比它们全都活得久,因为它对应的是人真正提问的方式。

先把粒度、一致性维度、SCD 搞对,你的仓库就能在长大后依然又快又可信。


推荐学习书籍 《CDA一级教材》适合CDA一级考生备考,也适合业务及数据分析岗位的从业者提升自我。完整电子版已上线CDA网校,累计已有10万+在读~ !

免费加入阅读:https://edu.cda.cn/goods/show/3151?targetId=5147&preview=0

数据分析师资讯
更多

OK
客服在线
立即咨询
客服在线
立即咨询