03 数据库设计范式应用
示例数据表:电商业务原始记录流水¶
| 交易流水号 | 下单时间 | 客户姓名 | 客户手机 | 客户等级 | 客户所在省 | 商品名称 | 商品分类 | 商品品牌 | 单价 | 购买数量 | 店铺名称 | 店铺掌柜 | 店铺所在市 | 促销活动 | 支付方式 | 支付流水 | 收货人姓名 | 收货人电话 | 收货地址 | 物流承运商 | 运单编号 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| TXN-04-01-01 | 2026-04-01 10:23 | 张三 | 13812340001 | 黄金会员 | 广东省 | 全麦吐司面包 | 食品饮料 | 桃李 | 8.50 | 2 | 桃李食品官方店 | 李掌柜 | 深圳市 | 满30减5 | 微信支付 | WX20260401001 | 张三 | 13812340001 | 广东省深圳市南山区... | 顺丰速运 | SF1001 |
| TXN-04-01-01 | 2026-04-01 10:23 | 张三 | 13812340001 | 黄金会员 | 广东省 | 纯牛奶(箱) | 食品饮料 | 蒙牛 | 45.00 | 1 | 蒙牛官方旗舰店 | 王掌柜 | 呼和浩特市 | 无 | 微信支付 | WX20260401001 | 张三 | 13812340001 | 广东省深圳市南山区... | 顺丰速运 | SF1001 |
| TXN-04-01-02 | 2026-04-01 14:55 | 李四 | 15988889999 | 钻石会员 | 北京市 | 蓝牙降噪耳机 | 数码电子 | 漫步者 | 399.00 | 1 | 漫步者北京专卖店 | 陈掌柜 | 北京市 | 新品首发优惠 | 支付宝 | ZFB20260401002 | 李四 | 15988889999 | 北京市朝阳区... | 京东物流 | JD2002 |
| TXN-04-01-02 | 2026-04-01 14:55 | 李四 | 15988889999 | 钻石会员 | 北京市 | 耳机收纳盒 | 数码配件 | 绿联 | 29.90 | 1 | 绿联官方旗舰店 | 赵掌柜 | 深圳市 | 无 | 支付宝 | ZFB20260401002 | 李四 | 15988889999 | 北京市朝阳区... | 京东物流 | JD2002 |
| TXN-04-02-01 | 2026-04-02 09:10 | 王五 | 17733221100 | 白银会员 | 上海市 | 全麦吐司面包 | 食品饮料 | 桃李 | 8.50 | 5 | 桃李食品官方店 | 李掌柜 | 深圳市 | 新客立减3元 | 信用卡 | CARD20260402001 | 赵六 | 18012349876 | 上海市浦东新区... | 圆通速递 | YT3003 |
| TXN-04-02-02 | 2026-04-02 20:30 | 张三 | 13812340001 | 黄金会员 | 广东省 | 蓝牙降噪耳机 | 数码电子 | 漫步者 | 399.00 | 1 | 漫步者北京专卖店 | 陈掌柜 | 北京市 | 无 | 微信支付 | WX20260402002 | 张三 | 13812340001 | 广东省深圳市南山区... | 顺丰速运 | SF1004 |
异常¶
- 插入异常:如果想在系统里录入一个新客户的信息(比如马七,手机号 166...),但他还没下过单。这张表插不进去,因为
交易流水号是空的(主键缺失)。 - 更新异常:如果
桃李食品官方店换了掌柜(变成孙掌柜),你需要把上表中所有包含该店铺的行(第1、2、5行)全部更新,漏一行就会数据不一致。 - 删除异常:如果业务要求删除流水
TXN-04-02-01(王五退货),删除这一行后,关于赵六(收货人)和圆通速递的运单信息就永远消失了。
分析¶
有了原始数据 → 提取语义上的函数依赖 → 使用 Armstrong 公理推导隐含依赖 → 识别出独立的业务数据域(实体)并进行规范化分解。
语义上的函数依赖¶
-
流水单维度:
交易流水号唯一决定下单时间、客户姓名、客户手机、客户等级、客户所在省、收货人姓名、收货人电话、收货地址、物流承运商、运单编号、支付方式、支付流水。- 注意:这里已经出现了传递依赖的苗头。比如
客户手机决定客户等级(假设手机号唯一标识客户)。
-
客户维度:
客户手机唯一决定客户姓名、客户等级、客户所在省。- 业务常识:手机号是唯一的自然键。
-
商品维度:
商品名称结合商品品牌唯一决定商品分类、单价。- 或者假设有隐形重复:
桃李品牌的全麦吐司面包就是食品饮料类。
-
店铺维度:
店铺名称决定店铺掌柜、店铺所在市。
-
物流维度:
运单编号决定物流承运商、收货地址(这里地址是从订单带下来的,但也依附于运单)。
第一步:从原始数据中提取已知的函数依赖(F)¶
我们不凭空想象,只根据业务常识和表格数据的模式来写:
| 依赖编号 | 函数依赖 | 业务解释 |
|---|---|---|
| FD1 | 交易流水号 → 下单时间, 客户手机, 收货人电话, 收货地址, 支付方式, 支付流水, 运单编号 |
一个流水号对应一笔交易的核心信息 |
| FD2 | 客户手机 → 客户姓名, 客户等级, 客户所在省 |
手机号唯一确定一位客户(假设手机号不重复) |
| FD3 | 商品名称, 商品品牌 → 商品分类, 单价 |
同一品牌下的同一商品名,分类和单价固定 |
| FD4 | 店铺名称 → 店铺掌柜, 店铺所在市 |
店铺名决定掌柜和所在城市 |
| FD5 | 运单编号 → 物流承运商, 收货地址 |
一个运单号对应固定的承运商和收货地址 |
注意:这里为了聚焦,把“交易流水号”当作主键的替代。实际上一个流水号里可能有多件商品,更严谨的主键是
(交易流水号, 商品名称, 商品品牌),我们稍后会处理这个部分依赖问题。
第二步:使用 Armstrong 公理推导隐含的函数依赖¶
这是你提到的核心环节。我们从已知的 FD1 ~ FD5 出发,用公理推导出那些数据表里看得到、但没直接写出来的依赖。
推导 1:交易流水号 → 客户等级 (传递律)¶
- 已知:FD1 有
交易流水号 → 客户手机 - 已知:FD2 有
客户手机 → 客户等级 - 应用传递律:
交易流水号 → 客户等级
业务含义:不用看客户手机,只要知道流水号,就能直接知道该客户是不是黄金会员。这会导致表中“客户等级”字段冗余存储。
推导 2:交易流水号 → 物流承运商 (传递律)¶
- 已知:FD1 有
交易流水号 → 运单编号 - 已知:FD5 有
运单编号 → 物流承运商 - 应用传递律:
交易流水号 → 物流承运商
业务含义:物流承运商依赖于运单号,运单号又依赖于流水号,形成传递依赖链条。
推导 3:(交易流水号, 商品名称, 商品品牌) → 单价 (增广律 + 伪传递思路)¶
- 主键其实应该是
(交易流水号, 商品名称, 商品品牌)的组合。 - 但 FD3 显示
(商品名称, 商品品牌) → 单价。 - 因为
(商品名称, 商品品牌)只是主键的一部分。 - 应用增广律:左边加上
交易流水号,右边加同样属性(无意义,但证明是部分依赖)。 - 结论:
单价仅依赖于主键的一部分(商品侧),这就是部分函数依赖。
推导 4:交易流水号 → 店铺掌柜 (传递链 + 增广)¶
- FD1 并未直接给出店铺。但在原始数据中,一行记录同时包含商品和店铺。我们可以假设业务规则:
(商品名称, 商品品牌) → 店铺名称(某品牌商品只在一家店卖)。 - 但更简单的是从数据看出:同一流水号下不同的商品行,
店铺名称可能不同(见原始数据 TXN-04-01-01 有桃李店和蒙牛店)。因此店铺名称其实依赖于(交易流水号, 商品名称, 商品品牌)中的商品部分。 - 因此存在
(商品名称, 商品品牌) → 店铺名称,进而(商品名称, 商品品牌) → 店铺掌柜。
第三步:计算候选键与闭包¶
为了规范化,我们需要知道真正的主键是什么。
设关系模式 R 包含所有属性。我们找出能决定全集的极小属性集。
- 从数据看,同一个
交易流水号可以有多行(因为买了多个商品)。 - 所以单凭
交易流水号不能唯一确定一行(不能决定商品名称和购买数量)。 - 候选键 =
(交易流水号, 商品名称, 商品品牌)
验证闭包:
(交易流水号, 商品名称, 商品品牌) → 通过 FD1 得到流水侧属性;通过 FD3 得到分类和单价;通过店铺依赖得到店铺信息。可以推导出全表所有属性。
第四步:根据依赖类型切分业务数据域¶
现在运用 2NF(消除部分依赖) 和 3NF(消除传递依赖) 进行拆分,拆分出的每一个新表就是一个独立的业务数据域。
| 违反范式 | 依赖示例 | 拆分出的业务数据域(新表) | 包含的原始属性 |
|---|---|---|---|
| 2NF 部分依赖 | 商品名称, 商品品牌 → 单价, 商品分类 |
商品域 | 商品名称, 商品品牌, 商品分类, 单价 |
| 2NF 部分依赖 | 商品名称, 商品品牌 → 店铺名称 (及后续传递) |
店铺域 | 店铺名称, 店铺掌柜, 店铺所在市 |
| 3NF 传递依赖 | 交易流水号 → 客户手机 → 客户等级 |
客户域 | 客户手机, 客户姓名, 客户等级, 客户所在省 |
| 3NF 传递依赖 | 交易流水号 → 运单编号 → 物流承运商 |
物流域 | 运单编号, 物流承运商, 收货地址, 收货人电话 |
| 主键保留 | 交易流水号, 商品名称, 商品品牌 |
交易流水主表 | 交易流水号, 下单时间, 支付方式, 支付流水 |
| 关联关系 | 主键拆开后剩下的连接关系 | 订单明细表 | 交易流水号, 商品名称, 商品品牌, 购买数量 |
结论¶
从一张纯文本的流水表,通过 Armstrong 公理的传递律和增广律推导隐含依赖,我们推导出了 5 个独立的业务数据域:客户、商品、店铺、物流、交易。
这正是你所说的:“推导出多个业务数据域”。Armstrong 公理在这里的作用是逻辑上的放大镜——它让你看到那些藏在数据重复背后的必然联系,从而用最科学的方式把一团乱麻的数据拆解成清晰的、低冗余的关系模式。