2. 从业务问题到数据模型
在零售场景中,业务人员提出的问题往往是一句自然语言,例如“我想知道最近哪些门店的销售额在下滑,哪些商品卖得好但利润低”。这句话不能直接变成一张表,也不能直接写成 SQL。数据建模工程的第一步,是把这类业务问题拆解成可被数据表达的结构:要回答什么、需要哪些指标、涉及哪些实体、每条记录代表什么、从哪些角度观察。本节以零售场景为例,走完从业务问题到概念模型、再到逻辑模型的完整路径。
一、需求拆解:把一句话拆成可建模的问题
需求拆解的目标不是复述业务原话,而是识别出问题中的分析对象、分析动作、分析范围和时间条件。以“最近哪些门店的销售额在下滑,哪些商品卖得好但利润低”为例,可以拆成两个子问题:
- 子问题 A:按门店、按时间观察销售额的变化趋势,识别下滑门店。
- 子问题 B:按商品观察销量与利润的组合关系,识别高销量低利润商品。
拆解后要明确边界:这里的“最近”需要定义成具体时间窗口,例如“最近 8 周,按周对比前 8 周”;“下滑”需要定义成可计算的判定条件,例如“本周销售额低于前 4 周移动均值的 90%”;“卖得好”需要定义成销量排名前 20%;“利润低”需要定义成毛利率低于 15%。这些定义不是业务人员随口说的,而是建模前必须与业务方确认的口径。口径不统一,后面所有模型和指标都会失去意义。
二、指标定义:从业务语言到可计算表达式
指标定义是把业务概念翻译成公式、字段和过滤条件的过程。零售场景中常见的核心指标包括:
| 指标名称 | 业务含义 | 计算表达式(示例) | 关键口径 |
|---|---|---|---|
| 销售额 | 商品实际成交金额 | SUM(销售数量 × 成交单价) | 剔除退货、取消订单;含税/不含税需明确 |
| 毛利额 | 销售收入减去商品成本 | SUM(销售数量 × (成交单价 − 单位成本)) | 成本取销售时点成本,不是当前成本 |
| 毛利率 | 毛利额占销售额比例 | 毛利额 ÷ 销售额 | 销售额为 0 时返回 NULL,不返回 0 |
| 销量 | 商品销售件数 | SUM(销售数量) | 按最小销售单位统计 |
| 周销售额环比 | 本周相对上周变化 | (本周销售额 − 上周销售额) ÷ 上周销售额 | 上周为 0 时单独标记 |
指标定义要落到字段级:销售额来自交易明细表的 quantity 与 unit_price,成本来自商品成本表的 unit_cost,时间来自交易日期字段。只有把指标拆到字段和过滤条件,才能判断现有数据是否支撑,以及是否需要补充采集。
三、实体关系识别:概念模型
概念模型关注业务对象及其关系,不关心具体字段类型和存储方式。零售场景中,围绕上述两个子问题,可以识别出以下核心实体:
- 门店:门店编号、门店名称、城市、区域、开业日期。
- 商品:商品编号、商品名称、品类、品牌、单位成本。
- 交易:交易编号、交易时间、门店、商品、数量、成交单价、金额。
- 时间:日期、周、月、季度、是否促销日。
实体之间的关系是:一个门店有多笔交易,一个商品出现在多笔交易中,一笔交易属于一个门店、一个商品、一个时间点。门店与商品之间通过交易形成多对多关系。概念模型可以用下面的结构关系图表示:
四、粒度设计:事实表每一行代表什么
粒度是维度建模中最关键的决定。它回答的是:事实表的一行代表什么业务事件。零售场景中常见粒度有三种:
- 交易明细粒度:一行 = 一笔交易中的一个商品。适合计算销售额、毛利额、销量,也支持按商品和门店下钻。
- 订单粒度:一行 = 一个订单。适合分析订单量、客单价,但无法直接按商品分析。
- 日汇总粒度:一行 = 门店 + 商品 + 日期。适合趋势分析,但会丢失单笔交易信息。
针对本节两个子问题,建议采用交易明细粒度作为基础事实表,因为子问题 B 需要按商品计算销量和毛利,子问题 A 需要按门店和时间观察趋势。如果只保留日汇总,虽然能算销售额,但无法回溯到具体商品成本,也无法处理退货和促销对单笔交易的影响。粒度一旦确定,后续所有指标都必须能在该粒度上聚合得到。
五、维度设计:从哪些角度观察
维度是观察事实的角度。围绕交易事实,可以设计以下维度:
| 维度 | 主要属性 | 支撑的分析问题 |
|---|---|---|
| 门店维度 | 门店编号、名称、城市、区域、业态 | 哪些门店销售额下滑 |
| 商品维度 | 商品编号、名称、品类、品牌、成本 | 哪些商品卖得好但利润低 |
| 时间维度 | 日期、周、月、季度、促销标识 | 最近 8 周趋势对比 |
| 促销维度 | 促销活动编号、类型、折扣力度 | 促销是否影响毛利 |
维度设计要避免两个极端:一是维度属性过少,导致无法下钻;二是把大量文本字段塞进维度,导致维度表膨胀。零售场景中,门店和商品维度应保留分析常用的层级属性,例如商品品类可以设计为“大类—中类—小类”三级,门店可以设计为“区域—城市—门店”三级。这样在逻辑模型中,就可以通过维度层级支持上卷和下钻。
六、逻辑模型:从概念到可实现的表结构
逻辑模型把概念模型转化为具体的表、字段和主外键关系。基于上述分析,可以设计如下逻辑模型:
-- 事实表:交易明细
fact_sales (
sales_id BIGINT, -- 交易明细主键
store_id BIGINT, -- 外键:门店
product_id BIGINT, -- 外键:商品
date_id INT, -- 外键:时间
quantity DECIMAL(18,2), -- 销售数量
unit_price DECIMAL(18,2), -- 成交单价
unit_cost DECIMAL(18,2), -- 销售时点单位成本
sales_amount DECIMAL(18,2), -- 销售额 = quantity * unit_price
gross_profit DECIMAL(18,2) -- 毛利额 = quantity * (unit_price - unit_cost)
)
-- 维度表:门店
dim_store (
store_id BIGINT PRIMARY KEY,
store_name STRING,
city STRING,
region STRING,
open_date DATE
)
-- 维度表:商品
dim_product (
product_id BIGINT PRIMARY KEY,
product_name STRING,
category_l1 STRING, -- 大类
category_l2 STRING, -- 中类
category_l3 STRING, -- 小类
brand STRING,
unit_cost DECIMAL(18,2) -- 当前成本,仅作参考
)
-- 维度表:时间
dim_date (
date_id INT PRIMARY KEY,
full_date DATE,
week_of_year INT,
month INT,
quarter INT,
is_promo_day BOOLEAN
)
逻辑模型确定后,就可以用 SQL 验证两个子问题。例如,识别最近 8 周销售额下滑的门店,可以先按门店和周聚合销售额,再用窗口函数计算前 4 周移动均值,最后筛选本周销售额低于移动均值 90% 的门店。识别高销量低利润商品,可以按商品聚合销量和毛利率,筛选销量排名前 20% 且毛利率低于 15% 的商品。这两个查询都能直接在 fact_sales 与维度表关联后完成,说明模型粒度与维度设计支撑了业务问题。
在 Databricks 中落地时,可以把 fact_sales 与维度表建成 Delta 表,利用 MERGE 处理增量交易,利用分区和 Z-Order 优化按日期和门店的查询。但这些都是物理实现层面的优化,前提是概念模型和逻辑模型已经正确。建模的顺序不能颠倒:先想清楚业务问题和粒度,再考虑存储和性能。