简介这份数据库课程设计资源围绕某送水公司的送水业务展开面向高校计算机相关专业学生及需要完成数据库课设的学习者帮助解决从需求分析到数据库落地的完整设计问题。资源包共3个文件包含1个doc设计报告、1个sql建库脚本和1个bak数据库备份文件压缩包约388KB报告内整合了建库代码、流程图与E-R图便于对照理解整体设计思路。内容覆盖工作人员与客户信息管理、矿泉水类别与供应商管理、入库出库管理等核心模块并实现了入库出库时对应类型矿泉水数量的触发器自动增减、按指定月份统计送水员工送水量的存储过程、查询指定月份用水量前10名用户并按递减排列的存储过程以及相关表之间的参照完整性约束。目前已有1543人学习下载适合作为数据库课设的参考方案与排错对照材料。1. 送水系统数据库课设从 .bak 到 .sql 的完整还原路径拿到「某送水公司的送水系统」这个压缩包时我第一反应不是急着打开 .doc 报告而是先看文件构成一个 .bak 备份、一个 .sql 脚本、一份 .doc 设计报告外加一个 .rar 打包。这套组合在数据库课程设计里非常典型——报告负责讲设计思路和 E-R 图.sql 负责建库建表.bak 负责还原一个能直接跑的完整库。很多人卡在第一步.bak 还原报错、.sql 执行到一半中断、报告里的表结构和脚本对不上。这篇笔记就按我实际拆包的顺序把「这是什么、怎么还原、参数怎么设、坑在哪」讲清楚适合正在做数据库课设、需要一份能跑通的送水业务案例的同学也适合想拿它练触发器、存储过程、参照完整性的熟手。这个系统的业务边界很清晰工作人员和客户信息管理、矿泉水类别和供应商管理、入库出库管理再往上叠三个数据库对象——入库出库触发器、按员工按月统计送水量的存储过程、按月查询用水量前 10 用户的存储过程。它不追求高并发也不涉及分布式价值在于把「表设计 约束 触发器 存储过程」这条课设主线走完整。下面从还原环境开始一步步落到能查询、能验证的状态。2. 还原环境与脚本执行.bak 和 .sql 两条路怎么选2.1 先判断你手里是哪种还原方式压缩包里同时给了 .bak 和 .sql这不是冗余而是两条不同的落地路径。.bak 是数据库备份文件还原后直接得到一个带数据的完整库适合想立刻看效果、验证触发器行为的人.sql 是建库脚本适合想逐行读建表语句、理解约束怎么写的同学。我的习惯是先用 .bak 快速还原一份能跑的库再用 .sql 在另一个库名上重建两边对照报告里的 E-R 图就能和实际表结构对上号。选 SQL Server 而不是 MySQL是因为 .bak 是 SQL Server 的备份格式报告里的触发器语法AFTER INSERT、INSERTED伪表也是 T-SQL 风格。如果你只有 MySQL 环境.sql 需要改写触发器和存储过程语法工作量不小建议直接装一个 SQL Server 2019 或 2022 开发者版Windows 上装完用 SSMS 连接最省事。2.2 .bak 还原的具体步骤还原 .bak 最容易翻车的地方是文件路径和逻辑文件名。SSMS 图形界面还原时如果直接点「确定」它默认还原到备份时的原始路径而那个路径在你机器上大概率不存在。正确做法是先查备份文件里的逻辑文件名再手动指定还原路径。-- 第一步查看备份文件里的逻辑文件名和物理路径 RESTORE FILELISTONLY FROM DISK D:\课设\某送水公司的送水系统.bak; -- 输出两行LogicalName 通常是 送水系统 和 送水系统_log -- PhysicalName 是备份时的原始路径仅作参考不要照抄-- 第二步用 MOVE 把数据文件和日志文件指到你自己的目录 RESTORE DATABASE 送水系统 FROM DISK D:\课设\某送水公司的送水系统.bak WITH MOVE 送水系统 TO D:\SQLData\送水系统.mdf, MOVE 送水系统_log TO D:\SQLData\送水系统_log.ldf, REPLACE, RECOVERY;第一段代码的RESTORE FILELISTONLY只读备份头不改任何东西用来确认逻辑名。第二段的MOVE是关键两个逻辑名必须和上一步输出完全一致大小写不敏感但拼写不能错。REPLACE表示目标库已存在时覆盖RECOVERY表示还原后直接可用。如果报「无法打开备份设备」检查路径里有没有中文空格必要时把 .bak 挪到纯英文路径再试。2.3 .sql 脚本执行的顺序问题.sql 脚本不能无脑全选执行因为建表有外键依赖顺序。常见做法是先执行建库和建表再执行触发器和存储过程最后插测试数据。如果脚本里已经用GO分批直接整体执行通常没问题如果报「外键引用不存在的表」说明建表顺序被打乱需要手动把被引用的表客户、员工、水类别、供应商放到前面。-- 建库时显式指定排序规则避免中文乱码 CREATE DATABASE 送水系统 COLLATE Chinese_PRC_CI_AS; GO USE 送水系统; GO -- 之后按 客户表 - 员工表 - 水类别表 - 供应商表 - 入库表 - 出库表 的顺序建COLLATE Chinese_PRC_CI_AS是中文环境下的常规选择CI 表示大小写不敏感AS 表示重音敏感。如果报告里的截图出现乱码多半是排序规则或字段类型用了varchar而非nvarchar把涉及中文的列改成nvarchar能解决大部分问题。3. 表结构与参照完整性E-R 图落到实际约束3.1 核心表清单与字段职责报告里的 E-R 图是概念模型落到物理表要关注主键、外键和业务字段。这个系统的表大致分四类基础信息表客户、员工、水类别、供应商、库存表入库、出库、以及可能存在的库存汇总表。下面这张表是我从 .sql 和报告对照后整理的核心结构字段名以脚本为准报告里叫法不同时以脚本为准。表名主键关键外键业务字段客户表客户编号无客户姓名、联系电话、地址员工表员工编号无姓名、性别、入职日期水类别表类别编号供应商编号类别名称、单价、库存数量供应商表供应商编号无供应商名称、联系人、电话入库表入库单号类别编号、员工编号入库数量、入库日期出库表出库单号类别编号、员工编号、客户编号出库数量、出库日期水类别表里的「库存数量」是触发器要维护的字段入库时加、出库时减。这个设计把库存冗余在类别表上查询当前库存不用聚合入库出库表代价是触发器必须保证一致性这也是课设要考的点。3.2 参照完整性约束怎么写参照完整性靠外键实现关键是删除和更新行为要选对。比如出库表引用客户表如果客户被删了历史出库记录不能跟着消失所以外键用ON DELETE NO ACTION而水类别引用供应商如果供应商停供且没有库存可以考虑ON DELETE SET NULL但课设里更常见的是统一用NO ACTION靠应用层控制。-- 出库表的外键定义示例 ALTER TABLE 出库表 ADD CONSTRAINT FK_出库_客户 FOREIGN KEY (客户编号) REFERENCES 客户表(客户编号) ON DELETE NO ACTION ON UPDATE CASCADE; ALTER TABLE 出库表 ADD CONSTRAINT FK_出库_水类别 FOREIGN KEY (类别编号) REFERENCES 水类别表(类别编号) ON DELETE NO ACTION ON UPDATE CASCADE;ON UPDATE CASCADE表示主键值改了子表跟着改课设里主键一般用自增或固定编号很少更新但写上更稳妥。ON DELETE NO ACTION是默认行为显式写出来是为了在报告里体现你考虑过删除策略。执行前先确认被引用列有主键或唯一约束否则报「引用列不是主键」。3.3 用系统视图验证约束是否生效建完表别急着插数据先用系统视图查一遍外键和触发器确认脚本真的执行到位了。-- 查所有外键约束 SELECT fk.name AS 外键名, OBJECT_NAME(fk.parent_object_id) AS 子表, OBJECT_NAME(fk.referenced_object_id) AS 父表 FROM sys.foreign_keys fk ORDER BY 子表; -- 查触发器和存储过程 SELECT name, type_desc FROM sys.objects WHERE type IN (TR, P) ORDER BY type_desc, name;第一段查外键确认每张子表都挂上了该有的约束第二段查触发器和存储过程TR是触发器P是存储过程。如果这里数量对不上报告说明脚本有部分没执行成功回滚重来比逐条补更省时间。4. 触发器与存储过程课设的三个核心对象怎么调4.1 入库出库触发器库存增减的自动维护触发器是这个课设的技术核心。入库时水类别表的库存数量要加出库时要减而且要考虑批量插入的情况——INSERTED伪表可能有多行不能只处理一行。CREATE TRIGGER TR_入库_增加库存 ON 入库表 AFTER INSERT AS BEGIN SET NOCOUNT ON; -- 按类别汇总本次插入的数量再更新库存 UPDATE w SET w.库存数量 w.库存数量 i.总数量 FROM 水类别表 w INNER JOIN ( SELECT 类别编号, SUM(入库数量) AS 总数量 FROM INSERTED GROUP BY 类别编号 ) i ON w.类别编号 i.类别编号; END;AFTER INSERT表示插入成功后触发SET NOCOUNT ON抑制行数提示避免干扰客户端。关键是子查询里先GROUP BY再SUM这样一次插入多条同类别记录也能正确累加。出库触发器同理把改成-但要在更新前判断库存是否足够否则会出现负库存。CREATE TRIGGER TR_出库_减少库存 ON 出库表 AFTER INSERT AS BEGIN SET NOCOUNT ON; -- 先检查库存是否充足 IF EXISTS ( SELECT 1 FROM 水类别表 w INNER JOIN (SELECT 类别编号, SUM(出库数量) AS 总数量 FROM INSERTED GROUP BY 类别编号) i ON w.类别编号 i.类别编号 WHERE w.库存数量 i.总数量 ) BEGIN RAISERROR(库存不足出库失败, 16, 1); ROLLBACK TRANSACTION; RETURN; END UPDATE w SET w.库存数量 w.库存数量 - i.总数量 FROM 水类别表 w INNER JOIN (SELECT 类别编号, SUM(出库数量) AS 总数量 FROM INSERTED GROUP BY 类别编号) i ON w.类别编号 i.类别编号; END;RAISERROR抛错后ROLLBACK回滚整个事务保证不会出现「扣了库存但出库记录没写」的半吊子状态。注意触发器里不要用SELECT *或返回结果集否则客户端可能报错。4.2 按员工按月统计送水量的存储过程这个存储过程接收员工编号和月份返回该员工当月的送水总数量。参数设计上月份用varchar(7)传2024-05这种格式最直观内部再转成日期范围。CREATE PROCEDURE SP_员工月送水量 员工编号 INT, 月份 VARCHAR(7) AS BEGIN SET NOCOUNT ON; DECLARE 开始日期 DATE, 结束日期 DATE; SET 开始日期 CAST(月份 -01 AS DATE); SET 结束日期 DATEADD(MONTH, 1, 开始日期); SELECT e.员工编号, e.姓名, ISNULL(SUM(o.出库数量), 0) AS 送水总量 FROM 员工表 e LEFT JOIN 出库表 o ON e.员工编号 o.员工编号 AND o.出库日期 开始日期 AND o.出库日期 结束日期 WHERE e.员工编号 员工编号 GROUP BY e.员工编号, e.姓名; END;用LEFT JOIN是为了让没有出库记录的员工也返回一行总量显示 0比直接INNER JOIN查不到更友好。日期范围用 开始 AND 下月一号避免BETWEEN在月末最后一天带时间部分时漏数据。调用方式EXEC SP_员工月送水量 员工编号 1, 月份 2024-05;。4.3 按月查询用水量前 10 用户的存储过程这个存储过程按月份聚合每个客户的出库总量倒序取前 10。TOP 10配合ORDER BY是 T-SQL 的标准写法注意TOP和ORDER BY要一起用才有意义。CREATE PROCEDURE SP_月用水前10客户 月份 VARCHAR(7) AS BEGIN SET NOCOUNT ON; DECLARE 开始日期 DATE, 结束日期 DATE; SET 开始日期 CAST(月份 -01 AS DATE); SET 结束日期 DATEADD(MONTH, 1, 开始日期); SELECT TOP 10 c.客户编号, c.客户姓名, SUM(o.出库数量) AS 用水总量 FROM 客户表 c INNER JOIN 出库表 o ON c.客户编号 o.客户编号 WHERE o.出库日期 开始日期 AND o.出库日期 结束日期 GROUP BY c.客户编号, c.客户姓名 ORDER BY 用水总量 DESC; END;INNER JOIN在这里合适因为没出库记录的客户不该出现在排行榜里。如果报告要求并列名次也显示可以改用RANK()窗口函数但课设层面TOP 10足够。调用EXEC SP_月用水前10客户 月份 2024-05;。5. 避坑与排查还原和运行中最容易翻车的五件事5.1 还原 .bak 报「媒体集有 2 个媒体簇」或路径错误现象执行RESTORE DATABASE时报「无法打开备份设备」或「媒体集有 2 个媒体簇但只提供了 1 个」。原因通常是 .bak 文件被压缩软件改过、路径含中文空格、或者备份时用了多个文件而这里只有一个。解决先把 .bak 复制到纯英文无空格路径如D:\bak\songshui.bak再用RESTORE FILELISTONLY确认能读出逻辑名如果仍报媒体簇错误说明文件本身不完整换用 .sql 脚本重建。5.2 .sql 执行到触发器报「数据库中已存在名为 TR_入库_增加库存 的对象」现象重复执行脚本时建触发器或存储过程失败。原因触发器和存储过程不像表那样每次重建重复执行会冲突。解决在建触发器前加DROP TRIGGER IF EXISTS建存储过程前加DROP PROCEDURE IF EXISTS或者先DROP DATABASE再整体重建。我一般用后者干净利落。5.3 触发器导致插入变慢或报「事务在触发器中结束」现象批量插入出库记录时客户端报「事务在触发器中结束批处理已中止」。原因触发器里用了ROLLBACK而外层还有事务回滚会连带外层一起回滚。解决要么在触发器里用RAISERROR但不ROLLBACK让外层应用决定要么确保调用时没有嵌套事务。课设里简单处理出库前先查库存别依赖触发器回滚。5.4 存储过程查不到数据但表里明明有记录现象EXEC SP_员工月送水量 员工编号 1, 月份 2024-5;返回空。原因月份格式必须是2024-05两位月份2024-5拼出来的日期是2024-5-01CAST可能失败或得到意外结果。解决统一用RIGHT(0 CAST(MONTH(开始日期) AS VARCHAR), 2)做格式化或者调用时严格传两位月份。另外检查出库日期字段类型如果是datetime带时间范围条件用和而不是BETWEEN。5.5 中文乱码或排序规则冲突现象插入中文客户名后显示问号或者跨库查询报「排序规则冲突」。原因建库时没指定Chinese_PRC_CI_AS或者字段用了varchar而非nvarchar。解决建库时显式指定排序规则中文列一律用nvarchar。已经建好的库可以用ALTER DATABASE 送水系统 COLLATE Chinese_PRC_CI_AS;改但已有数据可能需要重建表。6. 进阶验证用一组测试数据把三个对象跑通还原完、建完对象别只看报告截图自己造一组数据把触发器、存储过程、参照完整性全验一遍才算真正吃透。我一般按「插基础数据 → 插入库 → 查出库 → 调存储过程 → 试删主表」这个顺序走。-- 1. 基础数据 INSERT INTO 供应商表 VALUES (S001, 清源水厂, 张经理, 13800000001); INSERT INTO 水类别表 VALUES (W001, S001, 18.9L桶装水, 10.00, 0); INSERT INTO 员工表 VALUES (E001, 李明, 男, 2023-01-01); INSERT INTO 客户表 VALUES (C001, 王芳, 13900000001, 幸福路1号); -- 2. 入库 100 桶触发器应把库存从 0 改成 100 INSERT INTO 入库表 VALUES (R001, W001, E001, 100, 2024-05-10); SELECT 库存数量 FROM 水类别表 WHERE 类别编号 W001; -- 期望 100 -- 3. 出库 30 桶库存应变成 70 INSERT INTO 出库表 VALUES (O001, W001, E001, C001, 30, 2024-05-15); SELECT 库存数量 FROM 水类别表 WHERE 类别编号 W001; -- 期望 70 -- 4. 调存储过程 EXEC SP_员工月送水量 员工编号 E001, 月份 2024-05; EXEC SP_月用水前10客户 月份 2024-05; -- 5. 试删供应商应被外键拦住 DELETE FROM 供应商表 WHERE 供应商编号 S001; -- 期望报错第 2 步验证入库触发器第 3 步验证出库触发器第 4 步验证两个存储过程返回的数字和上面插入的一致第 5 步验证参照完整性真的在拦删除。如果第 5 步没报错反而删掉了说明外键没建上回第 3 章查sys.foreign_keys。这里有个血泪经验测试数据的主键值别用自增用固定编号S001、W001 这种这样删了重插不会因为自增跳号而对不上报告里的示例。另外每次重跑测试前先按外键逆序清空表或者直接DROP DATABASE重建比逐表DELETE省心。从那以后我每次拿到课设压缩包都强制先跑一遍「还原 → 查系统视图 → 造数据验证」这三步确认库是活的再去看报告不然报告写得再漂亮脚本跑不起来也是白搭。希望这份拆包笔记能帮你少走几个弯路。本文还有配套的精品资源点击获取