|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?用户注册
×
数据仓库建模:星型与雪花模型
一、具体的问题
很多业务系统跑了一两年,老板突然要"看板""日报""同比环比",开发第一时间想到的是在几张业务表上直接写 JOIN 出报表。结果报表一复杂,SQL 就几百行,查询动辄十几秒,改一个维度字段就要动整段 SQL。真正的根因不是 SQL 写得差,而是底层没有按"分析"的视角建模——你用的是面向交易(OLTP)的第三范式表,却想干面向分析(OLAP)的活。数据仓库建模要解决的,就是把"业务数据"整理成"好算、好扩、好理解"的分析结构,而星型模型(Star Schema)和雪花模型(Snowflake Schema)就是这套方法里最基础、也最该先搞懂的两种布局。
二、核心原理
1. 维度建模的基本构件
维度建模只分两类表:
- 事实表(Fact):存"发生了什么"——可度量的事件,如一笔订单的金额、数量、下单时间。特征是行数巨大、列少而稳定,包含两类列:度量(金额、数量,可加减)和外键(指向各维度)。
- 维度表(Dimension):存"从什么角度看"——时间、门店、商品、客户等描述性属性。特征是行数相对小、列多、变化慢。
2. 星型模型:扁平的辐射结构
星型模型里,事实表居中,周围直接连着非规范化的维度表——每个维度是一张宽表,所有属性都摊平在同一张表里。比如"商品维度"把类目、品牌、供应商、规格全放一张表,不再拆子表。整个结构像一颗星星:事实在中心,维度在四周,查询时事实表 JOIN 各维度表即可,JOIN 层数通常为 1。
优点很直接:查询简单、JOIN 少、性能高、BI 工具(如 Tableau、FineBI、Power BI)开箱即认。绝大多数数据仓库、数据集市默认都用星型。
3. 雪花模型:把维度再拆规范化的分支
雪花模型是星型的"规范化"版本:维度表里的冗余属性被进一步拆成子维度。还是"商品维度"的例子——类目单独成表、品牌单独成表、供应商单独成表,商品维度表只留外键指向它们。结构从星星变成了雪花(多层分支)。
它省了存储空间、消除了维度内的数据冗余(类目名只存一处),但代价是查询要多层 JOIN(商品→类目→品牌),SQL 更复杂、性能略低、对业务人员不友好。只有在维度极宽、且同一维度被多张事实表高度复用时,才值得上雪花。
4. 一个常被踩的坑:把维度表当业务表用
新手常犯两类错:一是把事实表也做成了"宽表"——把维度属性直接冗余进事实表,导致一旦维度更新,要改海量事实行;二是维度表过度拆到雪花,结果一个简单的"按品牌汇总销售额"要 JOIN 四张表。经验法则:事实表瘦、维度表胖但扁平,星型优先,雪花只在"维度被多个事实复用且更新频繁"时局部采用。
三、实例参考(动手步骤)
下面用一个"门店销售"场景,给出星型模型的建表与查询 SQL,并对比雪花模型差异。假设我们要分析:各门店、各商品类目、按天的销售额。
1) 星型模型建表(PostgreSQL 语法,其它库相似):
- -- 维度:时间(扁平,所有属性摊在一张表)
- CREATE TABLE dim_date (
- date_key INT PRIMARY KEY,
- full_date DATE,
- year INT,
- quarter INT,
- month INT,
- weekday INT
- );
- -- 维度:门店(扁平)
- CREATE TABLE dim_store (
- store_key INT PRIMARY KEY,
- store_name VARCHAR(50),
- city VARCHAR(30),
- region VARCHAR(20)
- );
- -- 维度:商品(星型:类目/品牌直接冗余在商品表里,不拆分)
- CREATE TABLE dim_product (
- product_key INT PRIMARY KEY,
- product_name VARCHAR(80),
- category VARCHAR(30),
- brand VARCHAR(30),
- supplier VARCHAR(40)
- );
- -- 事实:销售(只存度量与外键)
- CREATE TABLE fact_sales (
- sales_key BIGINT PRIMARY KEY,
- date_key INT REFERENCES dim_date(date_key),
- store_key INT REFERENCES dim_store(store_key),
- product_key INT REFERENCES dim_product(product_key),
- quantity INT,
- amount NUMERIC(12,2)
- );
复制代码
2) 星型查询:按"城市 + 类目"汇总 2026 年销售额——只需事实表 JOIN 三个维度,全部 1 层 JOIN:
- SELECT s.city,
- p.category,
- SUM(f.amount) AS total_amount,
- SUM(f.quantity) AS total_qty
- FROM fact_sales f
- JOIN dim_date d ON f.date_key = d.date_key
- JOIN dim_store s ON f.store_key = s.store_key
- JOIN dim_product p ON f.product_key = p.product_key
- WHERE d.year = 2026
- GROUP BY s.city, p.category
- ORDER BY total_amount DESC;
复制代码
3) 雪花对比:若把 dim_product 拆成 dim_product(category_key, brand_key, ...) + dim_category + dim_brand,上面那条查询就要再多 JOIN 两张表,SQL 变长、执行计划多两层,BI 自助分析时业务人员也更难理解。存储上雪花省了类目/品牌的重复字符串,但现代列存 + 压缩下这点收益通常抵不过查询复杂度。
4) 前后对比结论:星型在"查询性能 + 可理解性"上全面占优;雪花仅在"维度被多事实表复用(如销售事实和库存事实都引用同一商品类目)且类目结构经常整体调整"时,靠规范化减少同步成本。落地建议:先用星型把集市搭起来跑通,遇到具体复用痛点再局部雪花化,不要一上来就拆。
四、实操检查清单
- 事实表是否只含"度量列 + 各维度外键",没有任何描述性冗余属性?
- 维度表是否尽量扁平(星型优先),而非一上来就拆成多层雪花?
- 是否每个维度都有稳定的代理键(surrogate key,如 date_key/store_key),与业务自然键解耦?
- 粒度(grain)是否已明确定义——一行事实代表"一次销售明细"还是"一天一店汇总"?粒度乱了报表必错。
- 是否区分了"可加度量"(金额,可跨任意维度加)与"半加/不可加度量"(比率、余额,仅部分维度可加)?
- 是否给常用查询路径建了合适的索引/列存投影,并验证过查询耗时在可接受范围?
- 维度属性变更(如商品换类目)是否定义了 SCD 策略(覆盖/历史快照),避免历史报表失真?
- 雪花化之前,是否确认该维度确实被多张事实表复用、且更新频繁到值得付出查询复杂度代价?
|
|