2. 从业务问题到数据模型

在零售场景中,业务人员提出的问题往往是一句自然语言,例如“我想知道最近哪些门店的销售额在下滑,哪些商品卖得好但利润低”。这句话不能直接变成一张表,也不能直接写成 SQL。数据建模工程的第一步,是把这类业务问题拆解成可被数据表达的结构:要回答什么、需要哪些指标、涉及哪些实体、每条记录代表什么、从哪些角度观察。本节以零售场景为例,走完从业务问题到概念模型、再到逻辑模型的完整路径。

一、需求拆解:把一句话拆成可建模的问题

需求拆解的目标不是复述业务原话,而是识别出问题中的分析对象、分析动作、分析范围和时间条件。以“最近哪些门店的销售额在下滑,哪些商品卖得好但利润低”为例,可以拆成两个子问题:

  • 子问题 A:按门店、按时间观察销售额的变化趋势,识别下滑门店。
  • 子问题 B:按商品观察销量与利润的组合关系,识别高销量低利润商品。

拆解后要明确边界:这里的“最近”需要定义成具体时间窗口,例如“最近 8 周,按周对比前 8 周”;“下滑”需要定义成可计算的判定条件,例如“本周销售额低于前 4 周移动均值的 90%”;“卖得好”需要定义成销量排名前 20%;“利润低”需要定义成毛利率低于 15%。这些定义不是业务人员随口说的,而是建模前必须与业务方确认的口径。口径不统一,后面所有模型和指标都会失去意义。

二、指标定义:从业务语言到可计算表达式

指标定义是把业务概念翻译成公式、字段和过滤条件的过程。零售场景中常见的核心指标包括:

指标名称业务含义计算表达式(示例)关键口径
销售额商品实际成交金额SUM(销售数量 × 成交单价)剔除退货、取消订单;含税/不含税需明确
毛利额销售收入减去商品成本SUM(销售数量 × (成交单价 − 单位成本))成本取销售时点成本,不是当前成本
毛利率毛利额占销售额比例毛利额 ÷ 销售额销售额为 0 时返回 NULL,不返回 0
销量商品销售件数SUM(销售数量)按最小销售单位统计
周销售额环比本周相对上周变化(本周销售额 − 上周销售额) ÷ 上周销售额上周为 0 时单独标记

指标定义要落到字段级:销售额来自交易明细表的 quantity 与 unit_price,成本来自商品成本表的 unit_cost,时间来自交易日期字段。只有把指标拆到字段和过滤条件,才能判断现有数据是否支撑,以及是否需要补充采集。

指标定义建议同时记录三件事:公式、口径、负责人。口径包括时间范围、过滤条件、空值处理、单位。负责人用于后续口径变更时有人确认。

三、实体关系识别:概念模型

概念模型关注业务对象及其关系,不关心具体字段类型和存储方式。零售场景中,围绕上述两个子问题,可以识别出以下核心实体:

  • 门店:门店编号、门店名称、城市、区域、开业日期。
  • 商品:商品编号、商品名称、品类、品牌、单位成本。
  • 交易:交易编号、交易时间、门店、商品、数量、成交单价、金额。
  • 时间:日期、周、月、季度、是否促销日。

实体之间的关系是:一个门店有多笔交易,一个商品出现在多笔交易中,一笔交易属于一个门店、一个商品、一个时间点。门店与商品之间通过交易形成多对多关系。概念模型可以用下面的结构关系图表示:

零售场景概念模型:实体与关系 门店 Store 门店编号 城市 / 区域 开业日期 商品 Product 商品编号 品类 / 品牌 单位成本 交易 Transaction 交易编号 数量 / 成交单价 交易时间 金额 时间 Date 日期 / 周 / 月 是否促销日 1 : N N : 1 N : 1 关系说明 门店 1 : N 交易 商品 1 : N 交易 时间 1 : N 交易

四、粒度设计:事实表每一行代表什么

粒度是维度建模中最关键的决定。它回答的是:事实表的一行代表什么业务事件。零售场景中常见粒度有三种:

  • 交易明细粒度:一行 = 一笔交易中的一个商品。适合计算销售额、毛利额、销量,也支持按商品和门店下钻。
  • 订单粒度:一行 = 一个订单。适合分析订单量、客单价,但无法直接按商品分析。
  • 日汇总粒度:一行 = 门店 + 商品 + 日期。适合趋势分析,但会丢失单笔交易信息。

针对本节两个子问题,建议采用交易明细粒度作为基础事实表,因为子问题 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 优化按日期和门店的查询。但这些都是物理实现层面的优化,前提是概念模型和逻辑模型已经正确。建模的顺序不能颠倒:先想清楚业务问题和粒度,再考虑存储和性能。