【数据库】AI 设计规范化数据库结构
摘要:用 AI 帮你把一张乱表拆成清爽、不重复的规范数据库。
一、痛点引入
你有没有过这种表——一张 Excel 销售表,从左拉到右二三十列:订单号、客户、电话、地址、产品、单价、数量、销售员、部门……一个订单买三件东西,客户的姓名电话就得重复抄三遍。哪天要改一次客户电话,得满表找;更怕手滑把某一行的"单价"改串了,谁都不知道哪行才是真的。
这种"又宽又乱"的表,行话叫"未规范化"的表(也就是还没按规矩收拾过)。它短期能凑合用,可人一多、数据一涨,就成了出错的温床——改一处漏一处,对不齐、查不准。
咱们今天就用 AI(通义)当参谋,把这样一张乱表正正规规地拆一遍,拆成几张清爽、不重复的小表。不用死记硬背教科书,跟着做就行。
二、目标产出
学完这一篇,你能拿到三样东西:
- 一套规范结构:原来一张大宽表,被拆成 5 张各管各事的小表——客户表、产品表、销售员表、订单表、订单明细表。
- 清清楚楚的主外键:每张表哪个字段是主键(Primary Key,PK,能唯一锁定一行的小标签,比如"订单号"),哪个字段是外键(Foreign Key,FK,指向别家主键、用来"认亲戚"的字段,比如订单表里的"客户编号"指向客户表),一眼看明白。
- 一份可直接执行的建表 SQL:SQL(Structured Query Language,结构化查询语言,专门用来建库、建表、查数据的命令)。下面给的是 MySQL 语法,Access 也能改改就用。
顺带把三个"范式(Normal Form,NF,一套让表格不重复、不乱套的规矩)"——1NF、2NF、3NF——用人话讲明白,以后别人甩你一张表,你一眼能看出它卡在第几范式。
三、案例实战
3.1 先看看这张"乱表"长啥样
假设你是小公司负责订单的同事,手头有一张从系统导出来的「销售总表」。最原始的样子是这样的(注意最后一列"买的商品"):
| 订单号 | 下单日期 | 客户 | 客户电话 | 客户地址 | 买的商品(编号|名称|数量) | 销售员 | 销售员部门 |
|---|---|---|---|---|---|---|---|
| D20240001 | 2024-01-05 | 张三 | 13800001111 | 北京市朝阳区 | P001 机械键盘 1;P002 无线鼠标 2 | 小李 | 华北大区 |
| D20240002 | 2024-01-06 | 李四 | 13900002222 | 上海市浦东区 | P003 显示器 1 | 小王 | 华东大区 |
问题一眼就能看出来:
- "买的商品"这一个格子里塞了多个产品(编号、名称、数量混在一起),Excel 看着都费劲,更别说拿它去建库了。
- 一个订单买几件,信息就挤在一个格子里,没法分别统计"机械键盘卖了多少"。
这种"一个格子装多个值"的情况,正是没过第一范式的典型毛病。下面咱们用 AI 一步步拆。
3.2 第一步:让 AI 把表"摊平"到 1NF
1NF(第一范式)是什么:严格地说,1NF 看的是列原子性——每个格子(字段)只装一个值,不能再拆。上面那种"一个格子塞多个产品"的,先拆开。通俗记成"一行只说一件事"也行,但那只是把多值拆平后的效果,不是 1NF 的形式化定义(1NF 本身只看"格子是不是原子的")。
专业点说:1NF 要求字段是"原子的"(Atomic,不可再分的最小单位),并且不能有重复的"组"。
怎么做:把"买的商品"拆成"产品编号、产品名称、数量"三个独立字段,同一个订单买几件,就拆成几行。张三那单买了键盘+鼠标,就变成两行:
| 订单号 | 下单日期 | 客户 | 客户电话 | 客户地址 | 产品编号 | 产品名称 | 产品类别 | 单价 | 数量 | 销售员 | 销售员部门 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| D20240001 | 2024-01-05 | 张三 | 13800001111 | 北京市朝阳区 | P001 | 机械键盘 | 外设 | 299.00 | 1 | 小李 | 华北大区 |
| D20240001 | 2024-01-05 | 张三 | 13800001111 | 北京市朝阳区 | P002 | 无线鼠标 | 外设 | 99.00 | 2 | 小李 | 华北大区 |
| D20240002 | 2024-01-06 | 李四 | 13900002222 | 上海市浦东区 | P003 | 显示器 | 显示器 | 899.00 | 1 | 小王 | 华东大区 |
注意现在一张表的"钥匙"变成了两把拼一起:光"订单号"锁不住一行(同一个订单有两行),得加上"产品编号"才行。这种"两把钥匙拼一起当主键"的,叫复合主键(Composite Primary Key)——记住这个词,下一步要用。
到这儿,表已经过了 1NF:每个格子都是不可再分的一个值(列原子性达标)。拆平之后,直观表现就是"一行只说一件事(一个订单里的一件商品)"——但记住,这是拆平多值后的效果,1NF 本身只看格子是否原子。毛病还在——张三的信息在 D20240001 的两行里各抄了一遍,冗余得厉害。接着上 2NF。
3.3 第二步:让 AI 拆掉"部分依赖"到 2NF
2NF(第二范式)是什么:在 1NF 的基础上,不能"只看一半的钥匙就确定内容"。
专业点说:2NF 要求没有部分依赖(Partial Dependency)——也就是"非主键字段不能只依赖复合主键里的某一部分,必须依赖整把钥匙"。
对照上面的表,复合主键是(订单号,产品编号)。咱们挨个看非主键字段"听谁的":
- 客户、电话、地址、下单日期、销售员、部门:其实只看"订单号"这一半钥匙就能确定,跟"产品编号"半毛钱关系没有 → 这是部分依赖,要拆走。
- 产品名称、产品类别、单价:只看"产品编号"这一半钥匙就能确定,跟"订单号"无关 → 也是部分依赖,也要拆走。
- 数量:必须"订单号+产品编号"两把钥匙一起才确定(同一个订单同一个产品才有唯一数量)→ 它依赖整把钥匙,留着。
拆法(让 AI 按这个思路分表):
- 把"只跟订单号走"的信息,连同订单号,单独拉成一张订单表。
- 把"只跟产品编号走"的信息,连同产品编号,单独拉成一张产品表。
- 剩下的"订单号+产品编号+数量",单独成一张订单明细表(它就是那个多对多的桥)。
拆完长这样(先别管客户、销售员要不要再拆,那是 3NF 的事):
- 订单表:订单号(PK)、下单日期、客户、客户电话、客户地址、销售员、销售员部门
- 产品表:产品编号(PK)、产品名称、产品类别、单价
- 订单明细表:订单号 + 产品编号(合起来当复合主键)、数量
到这儿 2NF 达标:订单明细表里的每个非主键字段(目前只有"数量")都老老实实依赖整把钥匙了。
3.4 第三步:让 AI 拆掉"传递依赖"到 3NF
3NF(第三范式)是什么:在 2NF 的基础上,不能"绕个弯才确定内容"。
专业点说:3NF 要求没有传递依赖(Transitive Dependency)——也就是"非主键字段 A 决定了非主键字段 B,B 再决定 C,于是 C 是绕弯依赖了主键",这种情况要拆。
盯着刚拆出来的订单表看:里面有"销售员"和"销售员部门"两个字段。一个销售员只属于一个部门,所以"销售员 → 销售员部门"是成立的;而"销售员"本身又是跟着"订单号"(主键)走的。于是链条变成了:订单号 → 销售员 → 销售员部门。
这就叫传递依赖——"销售员部门"是绕了个弯才依赖主键的。3NF 要把它拆掉:把销售员单独提成一张销售员表,订单表里只留一个"销售员编号"去指向它。
同理,客户信息(客户、电话、地址)本质上属于"客户"这个独立的人/单位,也该有自己的表,别跟订单搅在一起——这样既去冗余,也避免客户换电话时满表改。于是再提成一张客户表,订单表里只留"客户编号"指向它。
最终 3NF 的 5 张表:
| 表名 | 字段 | 主键 / 外键 |
|---|---|---|
| 客户表 | 客户编号、客户姓名、客户电话、客户地址 | 客户编号(PK) |
| 产品表 | 产品编号、产品名称、产品类别、单价 | 产品编号(PK) |
| 销售员表 | 销售员编号、销售员姓名、销售员部门 | 销售员编号(PK) |
| 订单表 | 订单号、下单日期、客户编号、销售员编号 | 订单号(PK);客户编号(FK→客户表)、销售员编号(FK→销售员表) |
| 订单明细表 | 订单号、产品编号、数量 | (订单号,产品编号)复合主键;订单号(FK→订单表)、产品编号(FK→产品表) |
小提示:每张"实体表"(客户、产品、销售员)都配了一个稳定的编号当主键。别用"姓名"当主键——姓名会重名(两个"张三")、会改,当钥匙不靠谱。编号才是稳的。
3.5 设计图:表与表怎么"认亲戚"
把上面的关系画出来,就是一张清晰的"雪花"结构。表与表之间靠外键(FK)连起来:
┌──────────┐
│ 客户表 │
│ PK客户编号 │
└────┬─────┘
│ 1
▼ N
┌────────┐ ┌──────────────┐ ┌────────┐
│ 订单表 │1────N│ 订单明细表 │N────1│ 产品表 │
│ PK订单号 │ │ PK(订单号,产品编号)│ │ PK产品编号│
└───┬────┘ └──────────────┘ └────────┘
│ 1
▼ N
┌──────────┐
│ 销售员表 │
│ PK销售员编号│
└──────────┘关系一句话说清:
- 客户表 1 : N 订单表:一个客户能下多个订单。
- 销售员表 1 : N 订单表:一个销售员能接多个订单。
- 订单表 1 : N 订单明细表:一个订单里能有多件商品。
- 产品表 1 : N 订单明细表:同一件产品能出现在很多个订单明细里。
外键落点对应:
- 订单表.客户编号 → 客户表.客户编号
- 订单表.销售员编号 → 销售员表.销售员编号
- 订单明细表.订单号 → 订单表.订单号
- 订单明细表.产品编号 → 产品表.产品编号
3.6 一份可直接用的建表 SQL
下面这段是给 MySQL 的建表语句,复制进数据库就能跑(表名用中文)。若你的服务器标识符字符集支持中文(MySQL 8 默认 utf8mb4),建议给中文标识符包上反引号更稳(如 `客户表`)——下面示例为保持易读未逐一加反引号,你在自己库里加上即可;若仍不支持中文表名,再换成英文如 t_customer。每段都加了注释,照着看就懂:
sql-- 客户表:存"谁买的"
CREATE TABLE 客户表 (
客户编号 CHAR(6) PRIMARY KEY, -- 主键:唯一标识一个客户
客户姓名 VARCHAR(20) NOT NULL,
客户电话 VARCHAR(20),
客户地址 VARCHAR(100)
);
-- 产品表:存"卖的是什么"
CREATE TABLE 产品表 (
产品编号 CHAR(6) PRIMARY KEY, -- 主键:唯一标识一件产品
产品名称 VARCHAR(50) NOT NULL,
产品类别 VARCHAR(20),
单价 DECIMAL(10,2) NOT NULL -- DECIMAL 存金额,避免小数算错
);
-- 销售员表:存"谁卖的"(部门跟着销售员走,不进订单表,避免传递依赖)
CREATE TABLE 销售员表 (
销售员编号 CHAR(4) PRIMARY KEY,
销售员姓名 VARCHAR(20) NOT NULL,
销售员部门 VARCHAR(20)
);
-- 订单表:一次购买的"头信息"
CREATE TABLE 订单表 (
订单号 CHAR(10) PRIMARY KEY,
下单日期 DATE NOT NULL,
客户编号 CHAR(6) NOT NULL,
销售员编号 CHAR(4) NOT NULL,
-- 外键:认回客户表和销售员表
FOREIGN KEY (客户编号) REFERENCES 客户表(客户编号),
FOREIGN KEY (销售员编号) REFERENCES 销售员表(销售员编号)
);
-- 订单明细表:订单和产品的"多对多桥",用两把钥匙拼复合主键
CREATE TABLE 订单明细表 (
订单号 CHAR(10) NOT NULL,
产品编号 CHAR(6) NOT NULL,
数量 INT NOT NULL,
-- 复合主键:同一订单里同一产品只许出现一行
PRIMARY KEY (订单号, 产品编号),
FOREIGN KEY (订单号) REFERENCES 订单表(订单号),
FOREIGN KEY (产品编号) REFERENCES 产品表(产品编号)
);用 Access 的同学注意:Access 的 SQL 语法略有不同,主要三处——
- 文本类型用
TEXT(n)而不是VARCHAR(n),长文本用MEMO; - 日期类型用
DATETIME而不是DATE; - 自增主键用
AUTOINCREMENT(如ID AUTOINCREMENT PRIMARY KEY),外键约束建议在 Access 的"关系"视图里手动拖,比写 SQL 更稳。
其余的主键、外键、复合主键思路完全一样,照搬到 Access 里建表、建关系即可。
3.7 给通义的完整提示词(可直接复制)
把下面这段贴给通义,它就能替你出拆分方案和 SQL。你照着 3.2~3.6 的验收清单核对就行:
我有一张 Excel 销售表,原始字段是:订单号、下单日期、客户、客户电话、客户地址、买的商品(一个格子里写了"编号 名称 数量",可能含多件)、销售员、销售员部门。
请帮我做下面几件事:
1)先判断这张表现在违反了第几范式(1NF/2NF/3NF),说清楚违反在哪;
2)按 1NF → 2NF → 3NF 的顺序,一步一步把它拆成多张规范的小表,每一步说明"拆掉了哪种依赖";
3)给出每张表的表名、主键、字段,以及表与表之间的主外键关系;
4)给一份可直接执行的建表 SQL(MySQL 语法),表名可用中文,字段加中文注释;
5)最后告诉我,如果要在 Access 里实现,语法上要改哪几处。拿到回答后怎么验收(照着勾):
- 是否先判断了"卡在哪一范式",而不是一上来就甩 SQL?
- 拆分时有没有讲清"拆掉的是部分依赖还是传递依赖"?
- 每张表是否都有主键,跨表的字段是否用外键连上?
- SQL 能不能建成功(先在小测试库建两张表、插几行试试)?
- 原来"一个订单多件商品"的信息,拆完有没有丢?
四、原理小结
范式(NF)的本质就四个字——分而治之。每升一级,就消灭一类"数据重复/对不齐"的隐患:1NF 管"一个格子一个值",先把乱糟糟的多值拆平;2NF 管"别只看一半钥匙",把只跟某部分主键相关的信息请出去自成一张表;3NF 管"别绕弯",把"甲决定乙、乙决定丙"这种传递链条打断,让每个非主键字段都只直接听主键的话。一路拆下来,数据各归各表、靠编号(主键/外键)认亲戚,改一处只动一处,重复和矛盾自然就少了。
五、避坑指南
- 别一上来就死磕 3NF。 小项目、单人用、数据量小,拆到 2NF 往往就够用了。先让 AI 判断你卡在哪一范式,按需拆,别为"规范"而规范,把简单事搞复杂。
- 别在表里存"算出来的"列。 比如"金额 = 数量 × 单价"是算出来的,落库后容易跟明细对不上(改了数量忘了改金额)。要查金额就用查询(SELECT 数量*单价 …)或视图(View,存好的查询当表用)现算,别写死进表。
- 主键别用"会变的"字段当钥匙。 客户姓名会重名、会改,产品名也可能改。给每张实体表配一个稳定的编号(客户编号、产品编号、销售员编号)当主键,最稳。
- AI 给的 SQL 别直接上生产。 先在测试库建两张表、插几行、跑一条查询验证,确认能建、能查、关系没错,再正式用。AI 偶尔会把字段类型或外键方向写反,肉眼过一遍最保险。
- 范式不是越高越好。 做报表、跑统计时常用的"宽表"(把多张表拼成一大张),恰恰是故意反规范化(Denormalization)——牺牲一点规范换查询速度。规范化和性能要按场景取舍,别一刀切。
六、进阶延展
如果"什么是表、怎么在 Access 里建第一张表"还生疏,先回看 B13《设计数据表》 打地基;拆完表想自己写查询把数据捞出来,去 B14《AI 写查询(SQL)不求人》 学怎么跟 AI 要 SQL。
想再上一层、从"能建库"走到"用得溜",接下来的硬菜是:JOIN(多表联查,把拆开的表按关系拼回来)、索引(Index,给表加的"目录",让查询飞快)、视图(View,把常用查询结果存成一张"虚表"随用随取)——那是 L4 才展开的进阶内容,咱们下回见。