简介:这份资源是一套面向数据库课程设计与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_style | bookstyleno | 无 | 书籍类别 |
| system_readers | readerid | 无 | 读者信息 |
| system_books | bookid | bookstyleno | 书籍库存 |
| borrow_record | bookid | bookid, readerid | 借阅记录 |
| return_record | bookid | bookid, readerid | 还书记录 |
| reader_fee | bookid | bookid, 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 isborrowed='1'条件,防止重复更新。这个细节值得保留——如果一本书已经被借出,再次 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 isborrowed='0' WHERE bookid='909' AND isborrowed='1'; 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 readerid='Q001'就行。注意视图不存数据,只是查询的快捷方式。
再封装一个借书存储过程:
CREATE PROCEDURE sp_borrow_book @bookid VARCHAR(20), @readerid VARCHAR(9) AS BEGIN -- 先检查书是否在馆 IF EXISTS (SELECT 1 FROM system_books WHERE bookid=@bookid AND isborrowed='1') BEGIN INSERT INTO borrow_record (bookid, readerid, borrowdate) VALUES (@bookid, @readerid, GETDATE()); UPDATE system_books SET isborrowed='0' WHERE bookid=@bookid; SELECT '借书成功' AS 结果; END ELSE BEGIN SELECT '该书已被借出或不存在' AS 结果; END END;调用方式:EXEC sp_borrow_book '901', 'Q001';。存储过程把“检查—插入—更新”三步封在一起,调用方只需要传两个参数。这样做的好处是业务逻辑集中在数据库层,减少应用层出错概率。
验证存储过程是否生效,可以查视图:
SELECT * FROM v_borrow_detail WHERE bookid='901';如果 isborrowed 变成 '0',说明借书流程走通了。还书存储过程同理,把 INSERT 换成 return_record、UPDATE 改回 '1' 即可。
我自己的习惯是:每次改完存储过程,先拿一条测试数据跑一遍,再查视图确认状态字段变了,最后才去动正式数据。这个顺序帮我省过好几次后悔药——有一次忘了在存储过程里加事务,插入成功但更新失败,数据直接对不上。从那以后我每次封装业务逻辑都强制走一遍“测试数据 → 视图验证 → 正式执行”的流程。希望帮到你。
本文还有配套的精品资源,点击获取