
简介这份资源是一套面向数据库课程设计与SQL初学者的完整图书管理系统实现文档围绕图书馆日常业务中的借阅、归还与罚款等环节给出从需求分析到SQL落地的整套方案适合正在完成课程设计或希望系统练习关系型数据库建模的读者。压缩包内仅含1个doc文件约709KB集中承载了设计报告与完整代码便于一次性通读与整理。内容涵盖数据库存储结构设计、E-R图绘制、数据字典定义、关系模式推导以及SQL查询实现并细化了读者信息、图书信息、操作员信息三个基础模块以及借还书记录登记、罚款登记与借阅统计等业务功能读者可据此掌握实体关系梳理、关系代数运算与建表查询的完整流程。目前已有2317人学习下载适合作为数据库应用技术课程的参考范例与上机实践蓝本。1. 从一份课程设计报告说起SQL 数据库图书管理系统到底能跑出什么很多做数据库课设的同学手里都攥着一份类似的文档封面写着课程设计报告书正文里塞着 E-R 图、数据字典、建表语句和一堆 INSERT。这份《SQL 数据库图书管理系统(完整代码)》就是典型样本——它把读者、图书、借阅、还书、罚款五类业务串成一条完整链路用六张表撑起一个可运行的图书馆管理原型。适合谁正在做数据库课程设计、需要一份能直接跑通的 SQL Server 脚本参考的在校生或者想拿一个小型业务系统练手建表、外键、多表查询的初学者。它不涉及前端界面核心价值在于数据库层表结构怎么定、外键怎么连、初始数据怎么灌、查询怎么写。把这套脚本吃透数据库课设的骨架就立住了。2. 六张表怎么搭从 E-R 图到建表语句的落地路径2.1 先理清实体关系再动手写 CREATE TABLE这份资源的数据库设计遵循一条很朴素的路径先画 E-R 图再转关系模式最后落成建表语句。实体有五个——书籍类别、读者、书籍、借阅记录、还书/罚款记录。关系模式里书籍类别和书籍是一对多读者和借阅记录是一对多书籍和借阅记录也是一对多。理解这层关系后面看外键才不会晕。关系模式整理成表就是关系模式主键外键说明book_stylebookstyleno无书籍类别system_readersreaderid无读者信息system_booksbookidbookstyleno书籍库存borrow_recordbookidbookid, readerid借阅记录return_recordbookidbookid, readerid还书记录reader_feebookidbookid, readerid罚款记录注意一个细节borrow_record、return_record、reader_fee 这三张表都把 bookid 设成了主键。这意味着同一本书在借阅表里只能出现一次还书之后如果想再借得先把旧记录处理掉。这是课设级别的简化设计实际生产系统不会这么干但作为练习够用。2.2 建库建表的完整 SQL 与参数说明先建数据库。原文用的是 SQL Server 语法文件路径写的是c:\实际执行时建议改成具体目录否则可能报错。-- 建库指定数据文件和日志文件的初始大小与增长策略 USE master GO CREATE DATABASE librarysystem ON ( NAME librarysystem_data, FILENAME D:\sql_data\librarysystem.mdf, -- 改成实际存在的目录 SIZE 10MB, MAXSIZE 50MB, FILEGROWTH 5MB ) LOG ON ( NAME librarysystem_log, FILENAME D:\sql_data\librarysystem_log.ldf, SIZE 5MB, MAXSIZE 25MB, FILEGROWTH 5MB ) GO参数说明SIZE是初始大小MAXSIZE是上限FILEGROWTH是每次自动增长量。课设数据量小10MB 足够。如果目录不存在SQL Server 不会自动创建会直接报错——这是新手第一个翻车点。接着建六张表。按依赖顺序来先建被引用的表book_style、system_readers再建引用方system_books、borrow_record 等。-- 书籍类别表类别编号做主键 CREATE TABLE book_style ( bookstyleno VARCHAR(30) PRIMARY KEY, bookstyle VARCHAR(30) ); -- 读者表借书证号做主键 CREATE TABLE system_readers ( readerid VARCHAR(9) PRIMARY KEY, readername VARCHAR(9) NOT NULL, readersex VARCHAR(2) NOT NULL, readertype VARCHAR(10), regdate DATETIME ); -- 书籍表外键指向书籍类别 CREATE TABLE system_books ( bookid VARCHAR(20) PRIMARY KEY, bookname VARCHAR(30) NOT NULL, bookstyleno VARCHAR(30) NOT NULL, bookauthor VARCHAR(30), bookpub VARCHAR(30), bookpubdate DATETIME, bookindate DATETIME, isborrowed VARCHAR(2), FOREIGN KEY (bookstyleno) REFERENCES book_style(bookstyleno) ); -- 借阅记录表双外键bookid 同时是主键 CREATE TABLE borrow_record ( bookid VARCHAR(20) PRIMARY KEY, readerid VARCHAR(9), borrowdate DATETIME, FOREIGN KEY (bookid) REFERENCES system_books(bookid), FOREIGN KEY (readerid) REFERENCES system_readers(readerid) ); -- 还书记录表结构同借阅表 CREATE TABLE return_record ( bookid VARCHAR(20) PRIMARY KEY, readerid VARCHAR(9), returndate DATETIME, FOREIGN KEY (bookid) REFERENCES system_books(bookid), FOREIGN KEY (readerid) REFERENCES system_readers(readerid) ); -- 罚款表记录超期罚款信息 CREATE TABLE reader_fee ( readerid VARCHAR(9) NOT NULL, readername VARCHAR(9) NOT NULL, bookid VARCHAR(20) PRIMARY KEY, bookname VARCHAR(30) NOT NULL, bookfee VARCHAR(30), borrowdate DATETIME, FOREIGN KEY (bookid) REFERENCES system_books(bookid), FOREIGN KEY (readerid) REFERENCES system_readers(readerid) );建表顺序不能乱。如果先建 borrow_record 再建 system_books外键引用会失败。另外isborrowed字段用 VARCHAR(2) 存“是否被借出”原文用 1 表示在馆、0 表示借出这个语义后面初始化数据时会用到。2.3 初始化数据INSERT 顺序与借阅状态联动灌数据也要讲顺序。先灌 book_style 和 system_readers再灌 system_books最后灌 borrow_record 并同步更新 isborrowed。-- 先灌类别 INSERT INTO book_style VALUES (1,修真小说); INSERT INTO book_style VALUES (2,穿越小说); INSERT INTO book_style VALUES (3,恐怖小说); -- 其余类别依此类推 -- 再灌读者 INSERT INTO system_readers VALUES (Q001,李雷,男,学生,2013-01-18 12:20); INSERT INTO system_readers VALUES (Q002,韩梅梅,女,学生,2013-01-20 13:33); -- 其余读者依此类推 -- 灌书籍isborrowed 初始为 1 表示在馆 INSERT INTO system_books VALUES (901,飘渺之旅,1,萧潜,鲜网,2005-09-01,2013-05-25,1); INSERT INTO system_books VALUES (902,唐朝好男人,2,多一半,新星出版社,2008-05-09,2013-05-26,1); -- 其余书籍依此类推 -- 灌借阅记录同时把对应书籍标记为已借出 INSERT INTO borrow_record VALUES (901,Q001,2013-01-18 12:20); UPDATE system_books SET isborrowed 0 WHERE bookid 901 AND isborrowed 1;这里有个联动逻辑每插入一条借阅记录就要把 system_books 里对应书的 isborrowed 改成 0。原文的 UPDATE 语句带了AND isborrowed1条件防止重复更新。这个细节值得保留——如果一本书已经被借出再次 UPDATE 不会产生副作用。3. 查询与业务操作从单表 SELECT 到多表关联3.1 单表查询与条件筛选最基础的查询就是查全表。原文演示了SELECT * FROM book_style和SELECT * FROM system_books。实际用的时候更常见的是带条件的查询。-- 查所有在馆书籍 SELECT bookid, bookname, bookauthor FROM system_books WHERE isborrowed 1; -- 按类别查书 SELECT b.bookid, b.bookname, s.bookstyle FROM system_books b JOIN book_style s ON b.bookstyleno s.bookstyleno WHERE s.bookstyle 科幻小说;第一条语句用isborrowed 1过滤出在馆书籍。第二条用 JOIN 把书籍表和类别表连起来按类别名称筛选。注意 JOIN 的条件是b.bookstyleno s.bookstyleno这是两张表的公共字段。3.2 借阅、还书、罚款的业务 SQL 怎么写借书操作分两步插入借阅记录更新书籍状态。-- 借书先插入借阅记录 INSERT INTO borrow_record (bookid, readerid, borrowdate) VALUES (906, Q003, GETDATE()); -- 再把书标记为已借出 UPDATE system_books SET isborrowed 0 WHERE bookid 906 AND isborrowed 1;还书操作同样两步插入还书记录更新书籍状态回 1。-- 还书插入还书记录 INSERT INTO return_record (bookid, readerid, returndate) VALUES (906, Q003, GETDATE()); -- 恢复在馆状态 UPDATE system_books SET isborrowed 1 WHERE bookid 906;罚款登记则是在还书时判断是否超期。假设借期 30 天超期每天罚 0.5 元-- 查询超期未还的记录 SELECT br.bookid, br.readerid, br.borrowdate, DATEDIFF(DAY, br.borrowdate, GETDATE()) AS 已借天数 FROM borrow_record br WHERE DATEDIFF(DAY, br.borrowdate, GETDATE()) 30; -- 登记罚款 INSERT INTO reader_fee (readerid, readername, bookid, bookname, bookfee, borrowdate) SELECT br.readerid, r.readername, br.bookid, b.bookname, CAST((DATEDIFF(DAY, br.borrowdate, GETDATE()) - 30) * 0.5 AS VARCHAR(30)), br.borrowdate FROM borrow_record br JOIN system_readers r ON br.readerid r.readerid JOIN system_books b ON br.bookid b.bookid WHERE DATEDIFF(DAY, br.borrowdate, GETDATE()) 30;DATEDIFF(DAY, start, end)返回两个日期之间的天数差。罚款金额用(超期天数 - 30) * 0.5算出来再 CAST 成 VARCHAR 存进 bookfee 字段——因为原表把 bookfee 定义成了 VARCHAR(30)不是数值类型。这个设计不太合理但既然表已经这么建了插入时就得做类型转换否则会报类型不匹配。3.3 统计查询借阅排行与读者活跃度课设报告里通常要求有统计功能。按借阅次数排个序-- 统计每本书被借次数 SELECT bookid, COUNT(*) AS 借阅次数 FROM borrow_record GROUP BY bookid ORDER BY 借阅次数 DESC; -- 统计每位读者的借阅量 SELECT r.readername, COUNT(*) AS 借阅次数 FROM borrow_record br JOIN system_readers r ON br.readerid r.readerid GROUP BY r.readername ORDER BY 借阅次数 DESC;GROUP BY按 bookid 或 readername 分组COUNT(*)统计每组行数ORDER BY ... DESC降序排列。这两条查询在答辩时很能撑场面因为直接对应“书籍借阅情况统计”和“读者借阅情况统计”两个业务需求。4. 避坑与排查这份脚本跑不起来时先看这几条4.1 建库时报“文件路径无效”现象执行 CREATE DATABASE 时提示找不到路径或拒绝访问。 原因原文写的是FILENAME c:\只给了盘符没给文件名而且 C 盘根目录通常没有写入权限。 解决改成完整路径比如D:\sql_data\librarysystem.mdf并确保目录已手动创建。4.2 建表时外键引用失败现象CREATE TABLE borrow_record 时报“引用了无效的表”。 原因system_books 或 system_readers 还没建或者建表顺序反了。 解决严格按 book_style → system_readers → system_books → borrow_record → return_record → reader_fee 的顺序执行。4.3 INSERT 时报字符串截断现象插入读者姓名或书名时提示“将截断字符串或二进制数据”。 原因readername 定义的是 VARCHAR(9)中文占两个字节的情况下超过 4 个汉字就可能超长。 解决把 VARCHAR(9) 改成 VARCHAR(20) 或 NVARCHAR(20)后者对中文更友好。4.4 借阅记录插入后书籍状态没变现象插入了 borrow_record但 system_books 里 isborrowed 还是 1。 原因只执行了 INSERT忘了跟 UPDATE。 解决每次借书必须成对执行 INSERT UPDATE还书同理。建议包在事务里BEGIN TRANSACTION; INSERT INTO borrow_record VALUES (909,Q004,GETDATE()); UPDATE system_books SET isborrowed0 WHERE bookid909 AND isborrowed1; COMMIT;4.5 罚款金额算出来是整数现象超期 35 天罚款显示 2 而不是 2.5。 原因bookfee 是 VARCHAR 类型如果直接存数值SQL Server 可能做了隐式取整。 解决在计算时显式转成带小数的字符串比如CAST((天数-30)*0.5 AS DECIMAL(10,2))再转 VARCHAR。5. 进阶技巧用视图和存储过程把业务逻辑收进数据库课设做到后面老师往往会问“能不能把借书还书封装成存储过程”。这一步做了答辩时能多拿几分。先建一个借书视图把三张表连起来CREATE VIEW v_borrow_detail AS SELECT br.bookid, b.bookname, br.readerid, r.readername, br.borrowdate, b.isborrowed FROM borrow_record br JOIN system_books b ON br.bookid b.bookid JOIN system_readers r ON br.readerid r.readerid;视图的好处是查询时不用每次写三表 JOIN直接SELECT * FROM v_borrow_detail WHERE readeridQ001就行。注意视图不存数据只是查询的快捷方式。再封装一个借书存储过程CREATE PROCEDURE sp_borrow_book bookid VARCHAR(20), readerid VARCHAR(9) AS BEGIN -- 先检查书是否在馆 IF EXISTS (SELECT 1 FROM system_books WHERE bookidbookid AND isborrowed1) BEGIN INSERT INTO borrow_record (bookid, readerid, borrowdate) VALUES (bookid, readerid, GETDATE()); UPDATE system_books SET isborrowed0 WHERE bookidbookid; SELECT 借书成功 AS 结果; END ELSE BEGIN SELECT 该书已被借出或不存在 AS 结果; END END;调用方式EXEC sp_borrow_book 901, Q001;。存储过程把“检查—插入—更新”三步封在一起调用方只需要传两个参数。这样做的好处是业务逻辑集中在数据库层减少应用层出错概率。验证存储过程是否生效可以查视图SELECT * FROM v_borrow_detail WHERE bookid901;如果 isborrowed 变成 0说明借书流程走通了。还书存储过程同理把 INSERT 换成 return_record、UPDATE 改回 1 即可。我自己的习惯是每次改完存储过程先拿一条测试数据跑一遍再查视图确认状态字段变了最后才去动正式数据。这个顺序帮我省过好几次后悔药——有一次忘了在存储过程里加事务插入成功但更新失败数据直接对不上。从那以后我每次封装业务逻辑都强制走一遍“测试数据 → 视图验证 → 正式执行”的流程。希望帮到你。本文还有配套的精品资源点击获取