跳转至

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 公理推导隐含依赖 → 识别出独立的业务数据域(实体)并进行规范化分解。

语义上的函数依赖

  1. 流水单维度:

    • 交易流水号 唯一决定 下单时间、客户姓名、客户手机、客户等级、客户所在省、收货人姓名、收货人电话、收货地址、物流承运商、运单编号、支付方式、支付流水。
    • 注意:这里已经出现了传递依赖的苗头。比如 客户手机 决定 客户等级(假设手机号唯一标识客户)。
  2. 客户维度:

    • 客户手机 唯一决定 客户姓名、客户等级、客户所在省。
    • 业务常识:手机号是唯一的自然键。
  3. 商品维度:

    • 商品名称 结合 商品品牌 唯一决定 商品分类、单价。
    • 或者假设有隐形重复:桃李 品牌的 全麦吐司面包 就是 食品饮料 类。
  4. 店铺维度:

    • 店铺名称 决定 店铺掌柜、店铺所在市。
  5. 物流维度:

    • 运单编号 决定 物流承运商、收货地址(这里地址是从订单带下来的,但也依附于运单)。

第一步:从原始数据中提取已知的函数依赖(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 公理在这里的作用是逻辑上的放大镜——它让你看到那些藏在数据重复背后的必然联系,从而用最科学的方式把一团乱麻的数据拆解成清晰的、低冗余的关系模式。