ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

校园外卖系统数据库设计:四张表构建点餐闭环

校园外卖系统数据库设计:四张表构建点餐闭环 简介面向高校外卖场景的数据库设计文档覆盖需求分析、流程图、E-R图、数据定义到SQL建表与查询的完整流程适合作为数据库课程设计、毕业设计或入门学习参考资料。内容以餐厅、菜品、顾客、订单四个核心实体为主线详细说明各表的字段含义与作用并演示了价格范围筛选、嵌套查询、集合查询和视图创建等典型SQL操作。文档末尾还给出餐厅联系方式视图的创建示例便于快速查看餐厅信息。资源包内共1个docx文件大小1.92MB结构清晰可直接阅读并用于二次修改。目前已有3208人学习对希望快速掌握校园外卖系统数据建模思路并落地实践的同学具有较高参考价值。1. 校园外卖系统数据库四张表撑起一个完整的点餐闭环校园外卖和商圈外卖最大的区别在于场景固定学生集中在宿舍区配送半径不超过三公里收货地址几乎永远是“X栋X室”。互联网把点餐从打电话变成了系统下单但数据库要管的还是那四类数据——餐厅、菜品、顾客、订单。这份校园外卖系统数据库设计文档核心是 RESTAURANT、FOOD、GUEST、RFG 四张表外加一组贴近课程作业的查询与视图。如果你正在做数据库课设或想在一个小时内跑通从建表到查询的完整链路可以直接拿它当骨架。我按自己的习惯把 SQL 逐条过了一遍哪些能直接用、哪些必须改下面拆开讲。2. 需求分析与建模实体识别、E-R 映射与四张表的字段取舍2.1 实体识别先圈出名词再决定谁是一张表拿到需求文档我的习惯是先做一步“圈名词”把描述里反复出现的业务对象圈出来。这份文档第一部分写得很直白——“校园外卖必然包含提供外卖的餐厅、送外卖人员以及订购外卖的人员”。展开之后能圈出的实体正好四个餐厅提供名字、位置、联系方式、菜品菜单和价目、顾客联系方式、宿舍地址、订单反映餐厅与顾客之间的交易情况。这里有个容易忽略的点送外卖人员没有单独建表。文档的流程图是“客户订餐 → 餐厅 → 送餐”配送员在流程里出现了但在数据模型里被跳过了。如果你要扩展可以在 RFG 表上加一个配送员编号字段 RIDER_NO或者单独建 RIDER 表。但就课程设计而言四个实体已经能完整描述“下单—备餐—送达”的核心链路。四张表各自的职责在文档里写得很清楚餐厅表根据客户要求完成菜肴菜单表供客户选择顾客表记录享受餐食的人订单表反映交易。一句话总结就是“主数据 交易数据”前三张是相对静态的主数据RFG 是动态的交易流水。这个分类思路比背概念有用——以后再拿到一个业务需求先问“它是有独立生命周期的实体还是某两个实体之间的一次动作”答案决定了它是独立建表还是做成关联表。2.2 从 E-R 图到关系模型多对多联系拆出中间表文档里有一张手绘风格的 E-R 图实体包括餐厅、菜品、顾客联系上标了菜名、地址、电话、座号、数量这些属性。E-R 图到关系表的翻译规则是固定的每个实体变成一张表实体的属性变成表的列实体之间的联系则根据基数决定怎么落地。一对多的联系比如“一个餐厅有多道菜品”可以在多的一方加外键多对多的联系比如“一个顾客可以在多个餐厅点多个菜品”则需要拆出一张中间表。RFG 正是这种中间表RNO、FNO、GNO 三个编号组合起来表达“哪个顾客在哪个餐厅点了哪道菜”QTY 则是这个联系的属性——点了多少份。表名角色关键字段实体/联系RESTAURANT餐厅主数据RNO、RNAME、ADDRESS、PHONE实体FOOD菜品主数据FNO、FNAME、PRICE实体GUEST顾客主数据GNO、GNAME、ADDRESS、PHONE实体RFG订单交易RNO、FNO、GNO、QTY多对多联系这张表值得反复看当你不知道一个新需求该不该单独建表时先问它是实体还是联系。实体通常有独立生命周期——餐厅会新开、菜品会下架、学生会毕业联系则是“某个时间点发生的一次动作”。文档里的 E-R 图属性排得有点乱菜名和地址出现在同一个实体附近这是手绘图的常见问题。我的建议是自己按标准重画一遍矩形表示实体椭圆表示属性菱形表示联系确保每个字段都能在图上找到对应属性。这一步做扎实了后面的查询和视图才会顺。2.3 字段设计取舍为什么订单表只有四个编号RFG 只保留了三个编号加一个数量没有把餐厅名、菜名、顾客姓名冗余进来。这是比较规范的第三范式菜品名称由 FOOD 表维护餐厅名称由 RESTAURANT 表维护RFG 只存引用。好处是更新只需要改一处——比如“鱼香肉丝”从 8 元涨到 10 元只需要改 FOOD 表里那一行的 PRICE所有订单的 FNO 引用不受影响。代价是查询必须先关联。想看订单详情得把三张表 JOIN 起来这对数据量小的课程设计完全不是问题。但如果你准备把它扩展到生产环境就要权衡 JOIN 成本和冗余更新成本。常见的做法是在订单表里冗余一份“下单时的菜品名称和价格”快照防止商家改价之后历史订单显示也跟着变。这个决策没有对错只看需求要的是“热数据一致”还是“历史快照准确”。文档里数据定义部分还给了每个字段的类型长度比如 RNAME CHAR(50)、PHONE CHAR(15)。这里有个值得留意的信号它把“营业时间”SHIJIAN 也放进了餐厅表这说明餐厅表不只是静态主数据还包含了外卖场景下的服务信息。真实部署中这些字段应该在需求分析阶段就明确来源而不是写 SQL 时临时加——你在课上多花十分钟把字段来源对齐后面建表会少改很多次。3. 建表落地CREATE TABLE 与 INSERT 的细节和半成品修复3.1 四张表的 CREATE TABLE字段类型与约束逐条拆解文档给出的建表语句我按可执行顺序合并成了一段完整脚本-- 餐厅信息表RNO 作为唯一标识营业时间用字符型 CREATE TABLE RESTAURANT( RNO INT NOT NULL UNIQUE, RNAME CHAR(50), ADDRESS CHAR(50), PHONE CHAR(15), SHIJIAN CHAR(20) ); -- 菜品信息表价格用了 CHAR 类型后续查询需要小心 CREATE TABLE FOOD( FNO INT NOT NULL UNIQUE, FNAME CHAR(60), PRICE CHAR(20) ); -- 顾客信息表GNO 无约束GNAME 反而是唯一键 CREATE TABLE GUEST( GNO INT, GNAME CHAR(45) NOT NULL UNIQUE, ADDRESS CHAR(20), PHONE CHAR(30) ); -- 订单联系表四个裸 INT没有主键也没有外键 CREATE TABLE RFG( RNO INT, FNO INT, GNO INT, QTY INT );逐条说几个关键点。RESTAURANT 的 RNO 用了 INT NOT NULL UNIQUE但没有 PRIMARY KEY 声明。NOT NULL UNIQUE 在大部分数据库里会隐式建唯一索引语义上接近主键但它不叫主键一张表只能有一个主键但可以有多个唯一键。既然 RNO 在业务里就是餐厅的唯一标识直接写成 RNO INT PRIMARY KEY 语义更准确后续做外键引用时也能被数据库明确识别。GUEST 表有个容易看漏的细节GNO 没有加非空约束反而 GNAME 加了 NOT NULL UNIQUE。这说明文档把“顾客姓名”当作业务上的唯一标识。这在真实校园场景里有风险——不同宿舍楼完全可能有同名同姓的学生姓名做唯一键会导致第二个“张伟”插入失败。如果你的设计目标是“人能对上号”应该用 GNO 做编号主键GNAME 只保留普通索引。RFG 表四个字段都是裸 INT这是文档最需要补强的地方没有主键意味着同一行数据可以重复插入同一个顾客可以重复提交同一条订单没有外键意味着可以插入一个不存在的 FNO、GNO 或 RNO订单数据无法保证引用完整性。课程设计阶段的表格这样写可以跑通查询但如果老师追问“你怎么保证数据一致性”外键约束是必须补上的。PRICE 用 CHAR(20) 也是一个值得商榷的选择。CHAR 是定长字符串存数字时会带来两个问题一是比较运算按字典序而不是数值大小二是排序时 10 会排在 8 前面。这个问题在查询部分会展开讲建表时最好的做法是直接定义成 DECIMAL(6,2) 或 NUMERIC(10,2)精确到分也方便做聚合计算。3.2 初始数据插入原文档的 INSERT 为什么跑不通文档在“表的创建”之后贴了几条分散的 INSERT我按顺序整理出来-- 原文档插入语句直接执行会报错字段数与值数不匹配 INSERT INTO RESTAURANT VALUES(01, yuxianrousi, 8); INSERT INTO FOOD VALUES(03, youlinqiezi, 8); INSERT INTO FOOD VALUES(04, zhousha, 14-415, 693916); INSERT INTO RFG VALUES(01, 03, 01, 01); INSERT INTO RFG VALUES(04, 01, 03, 01); INSERT INTO RFG VALUES(03, 02, 04, 01);这里必须提醒一句这些语句直接执行会报错。RESTAURANT 表有 RNO、RNAME、ADDRESS、PHONE、SHIJIAN 五列但 INSERT 只给了两个值FOOD 表有三列第二条给了两个值、第三条却给了四个值。而且 8 出现在 RESTAURANT 的 ADDRESS 列位上14-415 出现在 FOOD 的 PRICE 列位上——这些值明显是文档编辑时的错位照抄必翻车。要让它跑通需要先明确每列对应的值。根据数据定义和后面的查询需求我补全了一版可行的插入语句-- 补全后的餐厅数据五列对齐 INSERT INTO RESTAURANT(RNO, RNAME, ADDRESS, PHONE, SHIJIAN) VALUES (01, yuxianrousi, 8号楼底商, 13800000001, 09:00-21:00); -- 补全后的菜品数据三列对齐 INSERT INTO FOOD(FNO, FNAME, PRICE) VALUES (01, yuxianrousi, 8), (02, gongbaojiding, 10), (03, youlinqiezi, 8); -- 顾客数据编号、姓名、宿舍地址、电话 INSERT INTO GUEST(GNO, GNAME, ADDRESS, PHONE) VALUES (01, LANSHUANGYAN, 14-415, 67312345), (02, XUQIHUI, 12-302, 67356789), (03, ZHOUsha, 14-415, 67323456), (04, LIUMEI, 14-416, 67391234); -- 订单数据餐厅、菜品、顾客、数量的组合 INSERT INTO RFG(RNO, FNO, GNO, QTY) VALUES (01, 03, 01, 01), (04, 01, 03, 01), (03, 02, 04, 01);补全的逻辑说明INSERT 有两种写法一是 VALUES 按表结构顺序给值二是显式写列名。显式列名的方式在表结构调整时更安全——即使将来加了新列只要这里没引用它语句就不会因为列数不匹配而报错。另外注意文档里 RFG 的插入用到了 RNO04、RNO03这意味着 RESTAURANT 表里必须存在 04、03 这两个餐厅否则加上外键约束后这些插入会被拒绝——原文档没加外键所以侥幸能插进去但这不是好事是完整性缺失的信号。3.3 主键与编号策略手工编号、自增列与业务唯一键文档里的编号是手工指定的RNO01、FNO03、GNO04 这样的小整数。演示没问题但真实环境里建议改用数据库自增列MySQL 的 AUTO_INCREMENT、SQL Server 的 IDENTITY、PostgreSQL 的 SERIAL。原因有两个一是高并发场景下应用层生成编号容易撞车需要额外的分布式 ID 方案二是删除中间行后手工编号需要人为维护连续性自增列则不需要业务方关心。如果你想把课程设计做出“业务规则”的味道还可以考虑在 GUEST 表里用学号作为唯一键。学号是定长数字比 GNO 自增列更像业务主键而且学生和顾客是一一对应的关系。但这个设计也有限制一个学生毕业后如果系统还在运行他的订单历史就会绑定在一个失效的学号上。常见做法是学号当业务唯一键、GNO 当代理主键两者同时保留互不冲突。建表阶段还要注意 CHAR 与 VARCHAR 的选择。CHAR(n) 是定长不足 n 的部分补空格VARCHAR(n) 是变长按实际长度存储。四张表里的名称、地址这类字段用 VARCHAR 更合理——餐厅名长短不一地址也是定长 CHAR 会导致存储浪费而且在某些数据库里 CHAR 比较时会因为尾部空格踩坑。电话号码用 VARCHAR 或 CHAR 都可以但注意不要把手机号当成 INT 存前导 0 会被丢掉超过 10 位的数字在 32 位 INT 里还会溢出。4. 查询实战价格筛选、短号匹配、嵌套查询与 UNION 的边界4.1 基础查询与价格区间BETWEEN 的隐式转换风险文档里第一个查询是看菜品和价格-- 查询所有菜品名称和价格 SELECT FNAME, PRICE FROM FOOD;这没什么好说的就是全表投影SELECT 列清单 FROM 表名。接下来是范围查询-- 查询价格在 8 到 10 之间的菜品 SELECT FNAME, PRICE FROM FOOD WHERE PRICE BETWEEN 8 AND 10;注意这里藏着一个“玄学”问题。PRICE 被定义成 CHAR(20)数据库在比较字符串和数字时会有隐式转换转换结果因数据库而异在 MySQL 里8、10 会被转成字符串按字典序比较字典序里 10 比 8 小因为先比第一个字符1 8所以价格 10 的菜可能被这个查询漏掉。看到这里你可以自己去验证一下同样的 BETWEEN 语句在不同数据库里跑出来的结果集合可能不一样。解决方式是在查询里显式转换-- 用 CAST 把 CHAR 转成数值再做区间比较 SELECT FNAME, PRICE FROM FOOD WHERE CAST(PRICE AS DECIMAL(6,2)) BETWEEN 8 AND 10;这是一个典型的“字段类型埋雷、查询阶段爆雷”的案例。建表时用 DECIMAL 就不会有这个问题字段已经错了就要在查询里用 CAST 兜底。两个方案都可行但后者每次查询都要转换全表扫描基本躲不掉数据量大了性能会吃亏。4.2 模糊查询与组合条件短号 LIKE 和 AND 查询的写法文档里有一条“查询短号为 67… 的顾客”SQL 片段是-- 用四个下划线匹配四位任意数字 SELECT GNO, GNAME, ADDRESS, PHONE FROM GUEST WHERE PHONE LIKE 673____;这里的 LIKE 用的是下划线而不是百分号。下划线在 LIKE 通配符里代表“恰好一个字符”四个下划线就代表四个任意字符。所以 673____ 实际匹配的是以 673 开头、总长度 7 位的电话号码。如果学生宿舍电话都是 7 位短号这个写法很精准如果目标数据是 11 位手机号673____ 就完全匹配不上。如果需求是“前三位是 673 就行”更稳妥的写法是-- 用百分号匹配任意长度后缀 SELECT GNO, GNAME, ADDRESS, PHONE FROM GUEST WHERE PHONE LIKE 673%;% 匹配任意长度字符包括零个673% 会把 673123、67312345 都捞出来_ 版本则严格限制位数。我一般会先确认电话号码的固定格式再选通配符——通配符写对了查询结果才符合预期。还要提醒一个性能问题LIKE 以通配符开头的写法比如 %673无法使用索引会全表扫描前缀匹配 673% 则有可能走索引。文档里还有一条“查询订餐地址为 14-415 且电话为 67… 的顾客”这是组合条件用 AND 连接-- 同时满足宿舍地址和电话前缀两个条件 SELECT GNO, GNAME, ADDRESS, PHONE FROM GUEST WHERE ADDRESS 14-415 AND PHONE LIKE 673____;AND 条件的执行顺序在逻辑上等价于“先过滤一个条件再过滤另一个”数据库优化器会自动调整顺序不需要人为关心。但这个查询里两个字段都没有索引实际就是全表扫两遍数据量小可以接受数据量大了要记得分别建索引。4.3 IN 嵌套查询先查出人再查出菜文档中的“带 IN 嵌套查询”原始片段只写了WHERE GNAMELANSHUANGYAN结合上下文最常见的意图是查某个姓名对应的顾客都点了哪些菜。标准写法是两层子查询-- 最内层按姓名查 GNO中间层查 FNO 集合外层取菜名 SELECT FNAME FROM FOOD WHERE FNO IN ( SELECT FNO FROM RFG WHERE GNO ( SELECT GNO FROM GUEST WHERE GNAME LANSHUANGYAN ) );执行逻辑很好理解最内层先按 GNAME 查出 GNO中间层用这个 GNO 在 RFG 里找到他点过的 FNO 集合最外层用 IN 把 FOOD 表里对应的菜名拿出来。IN 子查询的结果集在部分数据库里会先物化成临时表所以当子查询结果非常大时改写成 EXISTS 可能更高效-- EXISTS 逐行判断不物化子查询结果 SELECT FNAME FROM FOOD F WHERE EXISTS ( SELECT 1 FROM RFG R JOIN GUEST G ON R.GNO G.GNO WHERE G.GNAME LANSHUANGYAN AND F.FNO R.FNO );EXISTS 的语义是“外层每一行只要子查询能返回至少一行就命中”它不做结果集物化而是逐行相关判断。课程设计的数据量下两者性能差距几乎为零但答辩被问到“IN 和 EXISTS 的区别”时能说清楚“IN 先物化子查询、EXISTS 逐行判断”这一点印象分会明显不同。4.4 UNION 集合查询去重语义与列数一致性的坑文档里 UNION 部分的两条查询分别是“按地址 14-415 查”和“按电话 LIKE 673____ 查”合并后-- UNION 默认去重满足两个条件的顾客只出现一次 SELECT GNO, GNAME, ADDRESS, PHONE FROM GUEST WHERE ADDRESS 14-415 UNION SELECT GNO, GNAME, ADDRESS, PHONE FROM GUEST WHERE PHONE LIKE 673____;UNION 默认会去掉两条结果之间的重复行。也就是说如果有一个学生既住在 14-415、电话又以 673 开头他在结果里只出现一次。如果业务上希望保留重复比如想统计两个条件下的命中总数就要用 UNION ALL-- UNION ALL 保留全部行不去重 SELECT GNO, GNAME, ADDRESS, PHONE FROM GUEST WHERE ADDRESS 14-415 UNION ALL SELECT GNO, GNAME, ADDRESS, PHONE FROM GUEST WHERE PHONE LIKE 673____;UNION 还有一个容易被忽略的硬性要求两侧 SELECT 的列数必须一致列名以第一个 SELECT 的列名为准如果两侧数据类型不一致数据库会尝试隐式转换转不了就报错。在课堂作业里UNION 最常见的翻车场景就是两个 SELECT 列数不一样或者顺序不对——第一个 SELECT 查了四列第二个只查了三列直接报错。5. 常见避坑字段类型、外键缺失与视图问题的五条记录5.1 坑一PRICE 用 CHAR 存价格排序和区间查询全乱现象执行WHERE PRICE BETWEEN 8 AND 10结果里出现了 100 元以上的菜或者 10 元的菜查不出来ORDER BY PRICE的排序也不是按价格大小。原因PRICE 建表时定义为 CHAR(20)字符串比较按字典序100 排在 8 前面、10 也排在 8 前面数字的大小顺序在字符串世界里完全不成立。解决建表阶段把 PRICE 改成 DECIMAL(8,2)。如果表已经建好了查询时用 CAST 转换或者把数据迁移到新列。这是整个文档里最值得改的一处设计不改它后面所有价格相关的统计和排序都会带病运行。5.2 坑二RFG 表没有外键脏数据能插进去现象INSERT INTO RFG(RNO, FNO, GNO, QTY) VALUES(99, 99, 99, 1)执行成功但 RESTAURANT、FOOD、GUEST 里都没有 99 这个编号。原因RFG 的四个字段都只是裸 INT没声明 FOREIGN KEY数据库不会校验引用的编号是否存在。解决先补约束-- 给订单表的外键补上保证引用完整性 ALTER TABLE RFG ADD CONSTRAINT FK_RFG_RNO FOREIGN KEY (RNO) REFERENCES RESTAURANT(RNO), ADD CONSTRAINT FK_RFG_FNO FOREIGN KEY (FNO) REFERENCES FOOD(FNO), ADD CONSTRAINT FK_RFG_GNO FOREIGN KEY (GNO) REFERENCES GUEST(GNO);加了外键之后还有一步GNO 在 GUEST 表里最好有主键或唯一约束否则部分数据库会因为外键引用目标不明确而直接报错。修改完成后重新执行一次刚才的 INSERT 验证能拒绝才是对的。提示加外键之前先跑一次左连接查询确认现有数据里没有孤儿编号否则 ALTER TABLE 会因为脏数据而失败。5.3 坑三视图定义与业务意图不匹配更新还报错现象执行CREATE VIEW ADDRESS_RESTAURANT AS SELECT GNAME, PHONE FROM GUEST视图名叫“餐厅地址”查出来的却是顾客姓名和电话。再对这个视图执行 UPDATE数据库报“视图不可更新”。原因文档在视图的命名、列清单、来源表三者之间没有对齐——要展示餐厅地址应该 FROM RESTAURANT 而不是 FROM GUEST而且 SELECT 里没有包含能唯一定位行的主键视图就无法映射到底层表更新自然失败。解决视图只用来查询更新直接操作基表-- 更新基表不要尝试更新带 JOIN 或缺失主键的视图 UPDATE GUEST SET PHONE 67300000 WHERE GNAME LANSHUANGYAN;如果你的场景确实需要通过视图更新并且数据库支持 INSTEAD OF 触发器可以写触发器接管更新逻辑但课程设计不建议把复杂度堆在这里。视图定义本身也要改如果你真想做一个“餐厅地址视图”应该SELECT RNAME, ADDRESS FROM RESTAURANT视图名和业务意图对齐否则答辩时被问“为什么餐厅地址视图里是顾客姓名”会很尴尬。5.4 坑四CHAR 的尾部空格让数据对不上现象插入 RNAMEyuxianrousi 后用WHERE RNAMEyuxianrousi能查到但导出数据或用程序读取时字段后面多了一串肉眼看不见的空格。原因CHAR(50) 是定长字段不足 50 个字符时数据库自动补空格。不同数据库在字符串比较时对尾部空格的处理策略不一样MySQL 的排序规则默认忽略尾空格但程序端读取时空格是真实存在的。解决新开发一律用 VARCHAR已经在用 CHAR 的字段比较时用 RTRIM 包裹或者程序端读出来后主动 Trim。这个问题不致命但非常恶心——数据在数据库客户端里看起来完全正常导到 CSV 里一对长度才发现全带空格。5.5 坑五QTY 既没默认值也没有 CHECK订单数量失控现象插入订单时漏了 QTY表里出现 NULL或者插入 QTY0、负数也能成功。原因QTY 定义成 INT 时默认允许 NULL也没有 CHECK 约束限制取值范围。解决建表时应该写成-- 修正后的订单表编号非空、数量有默认值和范围校验 CREATE TABLE RFG( RNO INT NOT NULL, FNO INT NOT NULL, GNO INT NOT NULL, QTY INT NOT NULL DEFAULT 1 CHECK (QTY 0) );这里给三个编号字段也加上了 NOT NULL配合 5.2 的外键约束才能保证订单表的每一行都指向真实存在的餐厅、菜品和顾客。课程设计里如果老师检查数据完整性这几条是最容易拿分的点。6. 进阶技巧用约束、索引和视图把课设模型改造成可维护设计6.1 补主键与外键五条 ALTER 让四张表真正连起来前文一直在提“文档没有主键外键”现在给一个完整补丁-- 给四张表补主键再给订单表补三个外键 ALTER TABLE RESTAURANT ADD PRIMARY KEY (RNO); ALTER TABLE FOOD ADD PRIMARY KEY (FNO); ALTER TABLE GUEST ADD PRIMARY KEY (GNO); ALTER TABLE RFG ADD PRIMARY KEY (RNO, FNO, GNO); ALTER TABLE RFG ADD FOREIGN KEY (RNO) REFERENCES RESTAURANT(RNO); ALTER TABLE RFG ADD FOREIGN KEY (FNO) REFERENCES FOOD(FNO); ALTER TABLE RFG ADD FOREIGN KEY (GNO) REFERENCES GUEST(GNO);RFG 的复合主键 (RNO, FNO, GNO) 表达的业务语义是同一个顾客在同一家餐厅点同一道菜只保留一条记录QTY 表示份数如果点两份用 UPDATE 把 QTY 加 2而不是插入第二行。外键的作用是让数据库承担引用完整性校验不再允许悬空的编号。执行完这些 ALTER 之后顺手跑一次体检脚本-- 体检脚本找出三个外键指向不存在的孤儿数据 SELECT COUNT(*) FROM RFG R LEFT JOIN RESTAURANT A ON R.RNO A.RNO LEFT JOIN FOOD F ON R.FNO F.FNO LEFT JOIN GUEST G ON R.GNO G.GNO WHERE A.RNO IS NULL OR F.FNO IS NULL OR G.GNO IS NULL;返回 0 行说明现有数据干净加上外键约束后系统会一直保持这个状态。我一般把它叫“数据库体检脚本”每次给课设文档补约束之前先跑一遍避免 ALTER TABLE 因为已有脏数据而失败。6.2 建一个订单明细视图把 JOIN 留给数据库课程设计的查询大多教是“看订单详情”每次手写三个 JOIN 很容易写错。视图的本质是持久化的命名查询把复杂关联封装一次之后业务方直接 SELECT 视图-- 封装订单明细一次 JOIN 三张表对外暴露业务字段 CREATE VIEW V_ORDER_DETAIL AS SELECT R.RNAME, R.ADDRESS AS RESTAURANT_ADDRESS, F.FNAME, F.PRICE, G.GNAME, G.ADDRESS AS GUEST_ADDRESS, G.PHONE, RFG.QTY FROM RFG JOIN RESTAURANT R ON RFG.RNO R.RNO JOIN FOOD F ON RFG.FNO F.FNO JOIN GUEST G ON RFG.GNO G.GNO;使用视图之后业务方想查“兰双艳在鱼香肉丝那家餐厅点了什么”直接-- 业务方查询无需关心底层连接 SELECT * FROM V_ORDER_DETAIL WHERE GNAME LANSHUANGYAN;视图隐藏了底层连接逻辑也缩小了业务方出错的面积。但要记得 5.3 的教训这类带 JOIN 的视图通常不可更新只适合查询不适合 UPDATE。视图的创建语句里如果出现 DISTINCT、GROUP BY 或 UNION也会面临同样限制。下载这份 docx 后建议先建库再逐条跑查询脚本遇到报错就回来看第 5 章的五条记录。从那以后我每次拿到课设文档都会强制走一遍建表—插数—补约束—验证的完整流程而不是直接看查询写得好不好看。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进