数据仓库入门:从超市小票看懂数仓

很多同学第一次听到"数据仓库"四个字时,脑袋里浮现的是一排闪着蓝光的机柜、像《黑客帝国》里瀑布般滚动的绿色代码,或者干脆把它等同于"更大的 MySQL"。其实,它既不是神秘的黑科技,也不是简单的"大表"。数据仓库(Data Warehouse,下文简称"数仓")是一套专门用来回答"企业到底发生了什么"的方法论与工具集。为了把这套方法论写得既学术又接地气,本文把一次日常购物小票拆成三张图,用它们串起整个数仓的知识链。读完以后,你可以拿着小票给家人讲清楚:为什么超市要先结账再送积分、为什么电商大促前要"跑数"一整夜,以及为什么分析师总爱把"维度"挂嘴边。

目录

一、一张小票的旅程:业务系统->ODS->DW->DM

二、事实表:告诉系统"发生了什么"

三、维度表:告诉系统"谁、在哪里、是什么"

四、桥接表:解决"多对多"难题

五、分层建模:ODS->DWD->DWS->ADS


一、一张小票的旅程:业务系统->ODS->DW->DM

我们先看看一张超市小票上到底写了什么:

"2025-09-04 20:31,门店 A,顾客 135****8888,购买 5 件青岛纯生 500 ml,单价 6.5 元,实付 32.5 元,使用 5 元券,积分 27。"

这条记录首先落在超市的 POS 系统里,属于实时交易流水。POS 系统不关心历史趋势,它只关心库存别为负、收银别出错。为了做"昨天卖了多少啤酒"这种跨天分析,超市必须把这条记录搬到一个专门分析用的库------这就是数仓的起点。整条链路可以概括为:

层级 中文名 存储内容 面向对象 技术示例
ODS 操作数据存储 原始流水,几乎不改 数据开发 Kafka→Hive 外部表
DW 数据仓库层 清洗、整合后的明细 数据分析师 Hive/Spark SQL
DM 数据集市层 面向主题的汇总 业务人员 ClickHouse/StarRocks

ODS 层像快递站,只负责"原样签收";DW 层像厨房,把菜择好、切好;DM 层像自助餐台,把菜分门别类摆好,等人取用。小票进入 DW 层后,会被拆成三张表:事实表、维度表、桥接表。下文用这张小票做范例,把这三张表讲清楚。


二、事实表:告诉系统"发生了什么"

事实表的核心是"可加数值"和"外键"。小票里的可加数值是"数量 5""实付 32.5""优惠 5""积分 27"。外键则指向维度表:门店 A 对应门店维度表的一条记录,135****8888 对应顾客维度表的一条记录,青岛纯生 500 ml 对应商品维度表的一条记录。我们把这张事实表叫做 fct_sales,核心字段如下:

字段 含义 示例值
sales_id 交易唯一键 2025090420310001
dt 交易日期分区 2025-09-04
store_key 门店维度键 1001
customer_key 顾客维度键 987654
product_key 商品维度键 556677
quantity 销售数量 5
amt 实付金额 32.5
coupon_amt 优惠金额 5
point_amt 积分 27

事实表的设计遵循"星型模型":一张事实表周围环绕多张维度表,查询时通过外键 JOIN 即可。这样做的好处是查询逻辑清晰,坏处是维度表膨胀后 JOIN 代价高。业界用"分区+列存"解决:按 dt 做天级分区,再用 Parquet/ORC 列存,只扫需要的列。


三、维度表:告诉系统"谁、在哪里、是什么"

维度表保存"上下文"。没有维度表,事实表里的 1001、556677 只是一串冷冰冰的数字。维度表通常缓慢变化,所以 Kimball 提出"SCD"概念(Slowly Changing Dimension)。以商品维度为例:

字段 含义 示例值
product_key 代理键(无意义自增) 556677
sku_code 业务主键 6901234567890
sku_name 商品名 青岛纯生 500 ml
category_l1 一级品类 酒水
category_l2 二级品类 啤酒
brand 品牌 青岛啤酒
unit_price 当前售价 6.5
start_dt 生效日期 2025-07-01
end_dt 失效日期 9999-12-31
is_current 是否当前版本 Y

当青岛啤酒 500 ml 在 10 月 1 日涨价到 7 元时,我们不修改旧记录,而是插入一条新记录,把旧记录的 end_dt 改为 2025-09-30,is_current 改为 N。这种叫 SCD Type 2,可保留历史版本,便于历史回溯。维度表虽然变化慢,但体积往往比事实表大,因为商品、门店、顾客的数量级远高于日交易笔数。


四、桥接表:解决"多对多"难题

小票里还有一个隐藏的多对多关系:优惠券。一张优惠券可能对应多件商品,一件商品也可能被多张券分摊。为了不在事实表里制造"coupon1,coupon2,coupon3"这种丑陋的数组,我们引入桥接表 bridge_sales_coupon,字段如下:

字段 含义 示例值
sales_id 交易唯一键 2025090420310001
coupon_key 优惠券维度键 888
discount_amt 该券分摊金额 5

桥接表把事实表和维度表之间的多对多关系拆成一对多,既避免事实表过度宽化,又保留券粒度的分析能力。它本质上是"事实表的子表",有时也被归到"事实表家族"。


五、分层建模:ODS->DWD->DWS->ADS

Kimball 的星型模型解决"怎么摆菜",但企业级数仓还得回答"谁来择菜、谁来炒菜"。国内互联网公司普遍采用阿里提出的"四层模型":

层级 中文名 作用 与星型模型对应
ODS 操作数据层 贴源不清洗 不做模型
DWD 明细数据层 清洗、标准化 事实表+维度表
DWS 服务数据层 轻度汇总 宽表/汇总表
ADS 应用数据层 高度汇总 报表/接口

DWD 层的小票明细保留到分钟级粒度;DWS 层按"门店+品类"汇总到天;ADS 层直接吐出"昨日销售额"给老板手机。这样设计的好处是:下游改需求时,只需改 ADS 的 SQL,不必回头刷 ODS。


数据仓库不是一门高深莫测的技术,而是一套"把复杂留给自己,把简单留给业务"的工程哲学。下次当你在超市结账拿到小票时,不妨多看两眼:那条看似普通的记录,正在 ODS 层的 Kafka topic 里排队,等待被 DW 层的 Spark 任务拆成事实与维度,最终变成老板手机里的"昨日销售额"。理解了这条旅程,你就真正踏进了数据仓库的大门。祝学习愉快,欢迎把这篇博客转给同样对"数仓"二字心存敬畏的朋友。

相关推荐
咚咚王者3 小时前
人工智能之数据分析 numpy:第十三章 工具衔接与迁移
人工智能·数据分析·numpy
咚咚王者3 小时前
人工智能之数据分析 numpy:第九章 数组运算(二)
人工智能·数据分析·numpy
Yawesh_best3 小时前
告别系统壁垒!WSL+cpolar 让跨平台开发效率翻倍
运维·服务器·数据库·笔记·web安全
Ccjf酷儿5 小时前
操作系统 蒋炎岩 3.硬件视角的操作系统
笔记
习习.y6 小时前
python笔记梳理以及一些题目整理
开发语言·笔记·python
在逃热干面6 小时前
(笔记)自定义 systemd 服务
笔记
数据科学小丫6 小时前
数据分析与FineBI介绍
大数据·数据分析·finebi
可观测性用观测云7 小时前
采集华为云 CCI 日志到观测云最佳实践
数据分析
DKPT8 小时前
ZGC和G1收集器相比哪个更好?
java·jvm·笔记·学习·spring
QT 小鲜肉9 小时前
【孙子兵法之上篇】001. 孙子兵法·计篇
笔记·读书·孙子兵法