02 数据库设计范式
数据库范式¶
订单明细表
| 订单ID (PK) | 商品详情 (非原子列) | 客户信息 (非原子列) | 下单时间 | 折扣率 |
|---|---|---|---|---|
| O001 | P01:无线鼠标:89.00:2 , P02:机械键盘:299.00:1 |
C101, 张三, 黄金会员 | 2026-04-15 09:00 | 0.95 |
| O002 | P01:无线鼠标:89.00:3 | C102, 李四, 白银会员 | 2026-04-14 18:30 | 0.98 |
| O003 | P03:U盘64G:59.00:2 | C101, 张三, 黄金会员 | 2026-04-15 10:30 | 0.95 |
违反 1NF:在同一个单元格内填入多个值(非原子性),或者出现重复组列。
- 非原子性问题:“商品详情”列
- 非原子性问题:“客户信息”列
1. 1NF¶
将原表改造为符合1NF(所有属性原子化)
具体做法:
-
“商品详情”拆分为多行(每行一个商品),
-
“客户信息”拆分为原子列(客户ID、客户姓名、会员等级),
订单明细表¶
| 订单ID | 商品ID | 客户ID | 商品名称 | 单价 | 数量 | 下单时间 | 客户姓名 | 会员等级 | 折扣率 |
|---|---|---|---|---|---|---|---|---|---|
| O001 | P01 | C101 | 无线鼠标 | 89.00 | 2 | 2026-04-15 09:00 | 张三 | 黄金会员 | 0.95 |
| O001 | P02 | C101 | 机械键盘 | 299.00 | 1 | 2026-04-15 09:00 | 张三 | 黄金会员 | 0.95 |
| O002 | P01 | C102 | 无线鼠标 | 89.00 | 3 | 2026-04-14 18:30 | 李四 | 白银会员 | 0.98 |
| O003 | P03 | C101 | U盘64G | 59.00 | 2 | 2026-04-15 10:30 | 张三 | 黄金会员 | 0.95 |
- $f(商品ID)=商品名称、单价$
- $f(客户ID)=客户姓名、会员等级、折扣率$ ;
- $f(订单ID,商品ID)=下单时间、数量$
2. 2NF¶
1. 商品表(Product)¶
| 商品ID (PK) | 商品名称 | 单价 |
|---|---|---|
| P01 | 无线鼠标 | 89.00 |
| P02 | 机械键盘 | 299.00 |
| P03 | U盘64G | 59.00 |
2. 客户表(Customer)¶
| 客户ID (PK) | 客户姓名 | 会员等级 | 折扣率 |
|---|---|---|---|
| C101 | 张三 | 黄金会员 | 0.95 |
| C102 | 李四 | 白银会员 | 0.98 |
3. 订单明细表(Order)¶
| 订单ID (FK) | 客户ID(FK) | 商品ID (FK) | 数量 | 下单时间 |
|---|---|---|---|---|
| O001 | C101 | P01 | 2 | 2026-04-15 09:00 |
| O001 | C101 | P02 | 1 | 2026-04-15 09:00 |
| O002 | C102 | P01 | 3 | 2026-04-14 18:30 |
| O003 | C101 | P03 | 2 | 2026-04-15 10:30 |
如果说此表有三个键,那么依赖关系如下:
- $f(订单ID)=客户ID$
- $f(订单ID)=下单时间$
- $f(订单ID, 商品ID) = 数量$ // 完全函数依赖
存在部分依赖。因此,我们接着将其拆分为以下两个表。
3.1 订单表(Order)¶
| 订单ID (PK) | 客户ID (FK) | 下单时间 |
|---|---|---|
| O001 | C101 | 2026-04-15 09:00 |
| O002 | C102 | 2026-04-14 18:30 |
| O003 | C101 | 2026-04-15 10:30 |
- $f(订单ID)=客户ID$
- $f(订单ID)=下单时间$
3.2 订单详情表(OrderDetail)¶
| 订单ID (FK) | 商品ID (FK) | 数量 |
|---|---|---|
| O001 | P01 | 2 |
| O001 | P02 | 1 |
| O002 | P01 | 3 |
| O003 | P03 | 2 |
- $f(订单ID, 商品ID)=数量$
4. ER 图¶
erDiagram
Customer {
string 客户ID PK "C101"
string 客户姓名
string 会员等级
float 折扣率
}
Product {
string 商品ID PK "P01"
string 商品名称
float 单价
}
Order {
string 订单ID PK "O001"
string 客户ID FK
datetime 下单时间
}
OrderDetail {
string 订单ID FK "联合主键1"
string 商品ID FK "联合主键2"
int 数量
}
Customer ||--o{ Order : "拥有"
Order ||--o{ OrderDetail : "包含"
Product ||--o{ OrderDetail : "被引用"
关系说明:¶
- Customer — Order:一个客户可下多个订单(一对多)。
- Order — OrderDetail:一个订单包含多条商品记录(一对多)。
- Product — OrderDetail:一个商品可在多个订单明细中出现(一对多)。
3. 3FN¶
1. 客户表(Customer)¶
| 客户ID (PK) | 客户姓名 | 会员等级 | 折扣率 |
|---|---|---|---|
| C101 | 张三 | 黄金会员 | 0.95 |
| C102 | 李四 | 白银会员 | 0.98 |
由于存在以下传递依赖:
- $f(客户ID)=客户姓名$
- $f(客户ID)=会员等级$ #可能存在同名用户
- $f(会员等级)=折扣率$
不存在 $f(客户姓名)=等级ID$,比如,同名不同人的张三用户,不能一个X对应两个Y。
$f(张三)=L02$
$f(张三)=L01$
1.1 会员等级表(MembershipLevel)¶
| 等级ID (PK) | 等级名称 | 折扣率 |
|---|---|---|
| L01 | 黄金会员 | 0.95 |
| L02 | 白银会员 | 0.98 |
| L03 | 普通会员 | 1.00 |
- $f(等级ID)=等级名称$
- $f(等级ID)=折扣率$
1.2 客户表(Customer)¶
| 客户ID (PK) | 客户姓名 | 等级ID (FK) |
|---|---|---|
| C101 | 张三 | L01 |
| C102 | 李四 | L02 |
- $f(客户ID)=客户姓名$
- $f(客户ID)=等级ID$
2. ER 图¶
erDiagram
MembershipLevel {
string 等级ID PK "L01"
string 等级名称
float 折扣率
}
Customer {
string 客户ID PK "C101"
string 客户姓名
string 等级ID FK
}
Product {
string 商品ID PK "P01"
string 商品名称
float 单价
}
Order {
string 订单ID PK "O001"
string 客户ID FK
datetime 下单时间
}
OrderDetail {
string 订单ID FK "联合主键1"
string 商品ID FK "联合主键2"
int 数量
}
MembershipLevel ||--o{ Customer : "拥有"
Customer ||--o{ Order : "下单"
Order ||--o{ OrderDetail : "包含"
Product ||--o{ OrderDetail : "被引用"
关系说明(3NF 特定变化):¶
- MembershipLevel — Customer:一个等级对应多位客户,客户仅属于一个等级(一对多)。
- Customer — Order:一个客户可下多个订单(一对多)。
- Order — OrderDetail:一个订单包含多条商品明细(一对多)。
- Product — OrderDetail:一个商品可被多条订单明细引用(一对多)。
3NF 与 2NF 的核心区别:¶
- 新增
MembershipLevel表,消除了Customer表中的传递依赖(客户ID → 等级名称 → 折扣率),使折扣率仅由等级表直接管理。
4. 规范化过程验证¶
| 步骤 | 解决的问题 | 关键操作 |
|---|---|---|
| 1NF | 单元格非原子性,重复组 | 拆分商品行、拆分客户信息列 |
| 2NF | 非主属性对联合主键的部分依赖(商品信息只依赖商品ID,客户信息只依赖客户ID) | 抽出 Product 表、Customer 表,拆分 Order 与 OrderDetail |
| 3NF | 非主属性对主键的传递依赖(折扣率通过姓名间接依赖ID) | 抽出 MembershipLevel 表,客户表仅存外键 |
5. 再看3NF¶
-
复杂问题的解决必然需要复合函数。
-
3NF 不是消灭复合逻辑,而是将复合逻辑从“数据存储层”剥离,归还给“关系连接层”(Join操作)。
-
这就像神经网络:网络结构是深度的(复合的),但每一层权重矩阵内部是扁平的(无传递依赖的)。
- 每一层计算只存储该层的参数(权重),不要存储前向传播过程中的中间变量(激活值)。
神经网络视角下的对照表¶
| 概念 | 不满足3NF的宽表 | 3NF分解后的两张表 | 两层神经网络 |
|---|---|---|---|
| 函数 | 一个已经固化了中间结果的复合函数(如 $h = g(f(x))$) | 把复合函数还原为构成它的基本函数($f$ 和 $g$) | 前一层$f$与后一层$g$ |
| 结构 | 单表包含 $X \to Y \to Z$ | 表1: $X \to Y$;表2: $Y \to Z$ | 层1: $X \to Y$;层2: $Y \to Z$ |
| 计算 | 每个元组存储了 $Y$ 和 $Z$ 的计算快照 | 仅存储权重(外键参照),实时连接查 $Z$ | 仅存储参数 $W_1, W_2$,实时前向传播 |
| 冗余 | 极高(同系的地址存100遍) | 极低(地址只存1遍,系编号重复) | 无数据冗余(只有参数共享) |