
简介一份面向数据库课程设计与实训的完整参考文档围绕Oracle数据库实现图书管理系统的数据模型与SQL实现。文档从需求分析、设计目标与项目规划入手涵盖图书借阅、馆藏管理、读者管理等典型业务需求依次展开系统功能模块设计、数据库概念结构设计、逻辑结构设计和物理结构设计并具体给出创建表空间、数据表、视图、序列、索引、存储过程和触发器的操作方法同时覆盖数据查询、更新、合并及结果集合操作等典型数据库访问场景。资源包共1个文件类型为doc文档整体大小319KB内容结构清晰、附有详细目录便于按章节查阅和对照练习。已有281人学习浏览适合数据库初学者、高校学生以及需要完成课程设计或毕业设计的开发者参考完整呈现了从需求分析到落地实现的全过程既能辅助理解数据库设计理论又能指导Oracle环境下的实际开发可作为相关实训与项目开发的系统化范例。1. Oracle图书管理系统数据库一份把建库脚本写全的课程设计值得照着拆一遍如果你正在为数据库课程设计发愁“图书管理系统”这个题目已经算是被写烂也最容易翻车的题。网上搜出来的版本大多是几张表和几条查询交上去根本撑不起“设计”两个字的份量。这份《Oracle图书管理系统数据库设计与实现》不一样它是一份从需求分析一直写到DML操作的完整Oracle课程设计需求模块、E-R图、八张表、表空间、视图、序列、索引、存储过程、触发器以及插入、修改、删除、合并、集合操作全都给了SQL脚本。对正在赶课程设计、需要文档和脚本对得上的学生最有用对想拿一个小业务把Oracle入门到存储过程串起来的开发者也是现成的练手素材。它不完美但胜在步骤完整坑也足够典型值得照着拆一遍再决定改哪里。2. 系统分析与表结构设计从借还书业务到八张表的字段映射2.1 需求拆解功能模块和E-R模型先于SQL想清楚课程设计文档里把系统划分成管理员和读者两个主页面管理员管用户信息、图书、出版信息、副本读者查图书和副本整体上符合一个小型图书馆的核心操作链路。这里的关键不是页面而是从业务里抽实体图书、作者、出版社、分类、副本、用户、借阅。为什么要单独拆一个副本表因为同一本书可能采购多册馆藏编号要能区分物理上的一册书借出去的和在架的由副本编号跟踪而不是用书名去管。E-R关系大体是这样图书与作者是多对多通过Writers表连接图书属于某个分类通过ZNCode连接图书由出版社出版通过PubName连接一本图书对应多条副本记录一个读者可以借多条副本。这套模型是经典的三范式拆表优点是数据不冗余缺点是查询要走好几次连接实际复现时可以接受因为表和量级都不大。2.2 八张表的职责边界字段、主键和外键选型文档设计的八张表分别是Users用户、Books书籍、Copies副本、Authors作者、Categories分类、Writers写书关系、Publishers出版社、Borrow借阅。我先按文档里的逻辑设计整理成一张字段清单方便对照后面的建表脚本。表名职责关键字段主键/外键说明Books图书书目信息isbn, title, pubname, author, authorno, zncodeisbn主键pubname对应Publisherszncode对应CategoriesCopies每一册书copyno, isbncopyno主键isbn逻辑上应指向Books但建表时没加外键Authors作者表authorno, authornameauthorno主键Categories图书分类zncode, catenamezncode主键Writers作者与图书多对多关系isbn, authorno建表脚本里没做联合主键这是个遗漏Publishers出版社pubname, addresspubname主键Users读者/用户账号userno, username, userpwd, birth, quanxian, email, tel, addressuserno主键quanxian表示权限Borrow借阅记录文档只给了表名没展开字段复现时需要自己补copyno、读者编号、借期、还期从字段选型就能看出课程设计的老味道字符字段一律char定长编号字段用number。char在等值查询够用但一旦要存不定长字符串后面会有一堆空格问题这点我在第五章专门讲。另外Books里既出现了author这个冗余字段又保留authors表这在课程设计里很常见因为它既想展示多对多设计又想让人直接按作者名查书。实际项目里两个通道只能留一个。2.3 表与表之间的关系哪些是设计取舍哪些是粗心我拆这份文档时最在意的不是表建得多不多而是关系完不完整。Copies表应该有外键指向Books.isbn但建表脚本里没有后面作者用触发器去补删除同步属于“用触发器还外键的债”Writers表应该有联合主键避免同一作者和同一本书重复记录但建表脚本里也没写Users表里Quanxian字段用number表示权限虽然能用但语义不如varchar2写成admin/reader直接清楚。这些不算致命伤但复现时你要知道哪些是课程设计里的取舍哪些是粗心。前者保留后者建议改成约束或外键。带着这个底子去看第三章的CREATE TABLE你会明白每句SQL在干什么。3. 建库实操表空间、数据表、索引和第一轮DML冒烟测试3.1 表空间先给数据划地盘再决定autoextend策略Oracle里建表之前通常先建表空间这是它和MySQL最直观的差别。表空间对应物理数据文件相当于先给数据库划分一块地盘。文档里给了一段很典型的建表空间语句-- 创建名为data的表空间 create tablespace data logging datafile D:\oracle\product\10.2.0\oradata\orcl\data03.dbf size 50m reuse autoextend off;logging表示表空间内对象默认记录重做日志数据文件路径要换成你自己环境里的实际目录size 50m是初始50MBreuse表示如果文件已存在则重用autoextend off表示关闭自动扩展。我一般建议实际项目把autoextend打开加上maxsize 200m否则数据量上来后表空间写满插入直接报ORA-01653。当然课程设计数据量小关掉自动扩展反而更贴近考试要求这里按原文档复现即可。3.2 建表脚本八张表的DDL和约束逐个拆文档在建表部分分了七个create table我按原意整理成可执行的SQL块。先看Books、Copies、Authors-- Books表isbn作为主键其余字段按逻辑设计定义 create table Books ( isbn char(20) not null primary key, title char(30), pubname char(30), author char(30), authorno number(30), zncode number(30) ); -- Copies表copyno主键isbn本应外键指向Books原文档漏了 create table copies ( copyno number(10) not null primary key, isbn char(20) ); -- Authors表作者号主键作者名 create table Authors ( authorno number(10) not null primary key, authorname char(20) );not null primary key把非空和主键约束合在一起写是课程的常用简写。原文档里authorno number(30)和Authors.authorno number(10)精度不一致等值连接时Oracle会自动转换但遇到INTERSECT或UNION这类集合操作可能出类型不匹配建议复现时统一成number(10)。Copies.isbn没写外键后面要依靠触发器补删除同步这一点记住。接着是Categories、Writers、Publishers-- Categories表zncode是分类主键 create table Categories ( zncode number(20) not null primary key, catename char(20) ); -- Writers表isbn和authorno联合表达“谁写了哪本书”但漏了复合主键 create table Writers ( isbn char(20) not null, authorno number(20) not null ); -- Publishers表出版社名作主键 create table Publishers ( pubname char(30) not null primary key, address char(50) );Writers表漏了联合主键意味着同一对isbnauthorno可以重复插入多遍这在多对多关系表里属于脏数据隐患。建议补成alter table Writers add constraint pk_writers primary key (isbn, authorno);最后是Users表-- Users表UserNo主键Birthday为date权限字段用number create table Users ( UserName char(20) not null, UserPwd char(20) not null, UserNo number(12) primary key, Birth date not null, Quanxian number(20), Email char(30), TEL char(20), Address char(20) );Birth字段是date类型插入时必须用TO_DATE转换字符串不能直接塞字符串。Quanxian用number存储权限值在课程设计里够用但如果你想做更清晰的权限管理建议改成varchar2(10)值写admin或reader。3.3 索引建在WHERE和JOIN的字段上而不是每个列都建文档给了三个索引-- 图书表按书名建索引对应按书名查询的场景 create index Books_title_idx on Books(title); -- 用户表按姓名建索引对应按用户名登录/查询 create index Users_username_idx on Users(username); -- 副本表按副本编号建索引 create index Copies_copyno_idx on Copies(copyno);索引的选择逻辑基本正确热门查询字段是title、username、copyno。但Copies.copyno已经是主键Oracle主键会自动创建唯一索引再手动建普通索引属于重复建设不会报错但白占空间。这个细节可以作为答辩加分项主动删掉第三个索引改在copies.isbn上建索引因为外键关联查询更频繁。验证索引是否生效可以用数据字典视图-- 查看Books表上的索引 select index_name, table_name, uniqueness from user_indexes where table_name BOOKS;如果返回主键索引和Books_title_idx两条说明索引都建上了。3.4 插入、修改、删除建完表先来一轮DML冒烟表建完不能只停在DDL得插入数据验证。文档给了一批样例数据我挑三条有代表性的-- 插入一本图书 insert into Books(ISBN, Title, PubName, ZNCode, author, authorno) values(A0001, 草样年华, 长江文艺出版社, 1, 孙睿, 1); -- 插入一条对应副本 insert into copies(copyno, isbn) values(1001, A0001); -- 插入一个用户 insert into Users(UserName, UserPwd, UserNo, Birth, QuanXian, Email, TEL, Address) values(冯美, 123, 1, TO_DATE(1986-09-01,YYYY-MM-DD), 1, 530347830qq.com, 13550399250, hubei);TO_DATE把字符串按指定格式转换成日期是Oracle里给date列赋值的标准姿势。注意原文档Users插入语句里的字段顺序和值顺序有错位问题我会在第五章展开。修改和删除也很直接-- 修改用户编号为9的电话号码 update Users set TEL 1355041906 where userno 9; -- 删除名为罗莎的用户 delete from Users where username 罗莎;执行修改前最好先select * from users where userno9确认目标存在否则更新0行也不报错容易误以为成功。删除同理课程设计里没有外键约束时delete可以随意执行但真实系统一定要先查关联数据。4. 用Oracle对象封装逻辑视图、序列、存储过程、触发器与集合操作4.1 视图把常用查询固化成语义清晰的“假表”视图保存的是查询定义不占物理数据。文档建了三个视图分别解决不同查询场景-- 查看图书完整书目信息 create or replace view cx_books as select ISBN, Title, PubName, ZNCode, author, authorno from Books; -- 只看作家出版社的图书名称、作者和副本编号 create or replace view cx_zj as select title, author, copyno from Books, Copies where Copies.isbn Books.isbn and PubName 作家出版社; -- 查看作者为安妮宝贝的所有图书 create or replace view cx_anni as select * from Books where author 安妮宝贝;create or replace的好处是视图重跑不会报“对象已存在”的错误。cx_zj用了老式逗号连接在where里写关联条件语法没错但可读性不如join。建议改成create or replace view cx_zj as select b.title, b.author, c.copyno from Books b join Copies c on c.isbn b.isbn where b.pubname 作家出版社;cx_anni直接查整行适合快速排查数据但如果将来表结构变化select *视图要重建。4.2 序列生成编号的标准工具但不是为了无间隙序列用来生成唯一递增数字文档里的cx_un设计如下-- 从1开始步长1无上限不循环默认缓存20 create sequence cx_un increment by 1 start with 1 nomaxvalue nocycle;increment by 1是每次加1start with 1从1开始nomaxvalue不设上限nocycle表示达到上限后不循环。系统默认缓存20个序列值性能好但数据库异常关闭时会跳号。对于借阅单号、用户编号这类不要求肉眼连续的业务跳号无所谓如果有严格连续显示的凭证号就不要用序列。使用方式是在insert里调用-- 用序列生成用户编号 insert into Users(UserNo, UserName, UserPwd, Birth) values(cx_un.nextval, 测试用户, 123, TO_DATE(2000-01-01,YYYY-MM-DD));nextval是取下一个值currval是取当前会话刚生成的序列值后者在没调用过nextval时会报ORA-08002。4.3 存储过程BooksAdd把插入操作封装成可复用入口文档给了一个添加图书的存储过程-- 存储过程管理员添加图书时调用 create or replace procedure BooksAdd ( isbn in char, title in char, pubname in char, author in char, authorno in char, zncode in char ) as begin insert into Books values(isbn, title, pubname, author, authorno, zncode); end BooksAdd; /in表示输入参数参数类型写成char会跟表字段定义保持一致。问题是authorno和zncode在表里是number过程参数却写成了charinsert时Oracle做隐式类型转换字符串内容合法就能成功。真实项目建议把参数类型和表列完全对齐少让数据库猜。调用方式-- 调用存储过程添加图书 call BooksAdd(A0011, Oracle实战, 机械工业出版社, 张三, 7, 1);如果传入重复的isbninsert会因主键冲突报ORA-00001。想处理这个场景可以在过程里加异常块create or replace procedure BooksAddSafe (...) as begin insert into Books values(...); exception when dup_val_on_index then dbms_output.put_line(isbn已存在插入被忽略); end BooksAddSafe; /4.4 触发器删除图书时同步清理Copies副本记录由于Copies表没有外键原设计用触发器保证删除一致性-- 删除图书后同步删除该isbn对应的所有副本 create or replace trigger BooksDelete after delete on Books for each row begin delete from Copies where isbn :OLD.isbn; end BooksDelete; /after delete在删除动作发生之后触发for each row表示每删一行触发一次:OLD.isbn代表被删除行的旧值也就是删除前的isbn。这段逻辑能解决问题但更规范的做法是给Copies表加外键并启用级联删除-- 用级联删除替代触发器 alter table Copies add constraint fk_copies_books foreign key (isbn) references Books(isbn) on delete cascade;使用级联删除后删除Books记录会自动删掉对应Copies不需要触发器。课程设计里两种方案都可以写但答辩时最好说清为什么保留触发器否则会被问“有外键为什么还要触发器”。4.5 数据合并与集合操作count函数和INTERSECT的写法文档第四章还包含一个小统计函数和集合操作。统计作者数量的函数原意是这样的-- 统计图书表里的作者数量 create or replace function count_author return number as cnt number; begin select count(author) into cnt from Books; return(cnt); end count_author; /原文档把函数名直接写成count这是不好的习惯因为count是关键字/内置函数名容易引起调用歧义。这里我改成count_author。select count(author) into cnt表示把查询结果赋给变量cntOracle的select into要求返回且只能返回一行聚合函数正好满足。集合操作INTERSECT返回两个查询结果都有的行。文档里给了一段有代表性的写法-- 找作者号为2的图书与Writers表中isbn为A0006的交集 select authorno, isbn from Books where authorno 2 intersect select authorno, isbn from Writers where isbn A0006 order by authorno desc;intersect会去重且最终排序必须写在最后。这段查询的可行前提是Writers表里已经插入了对应数据原文档没给Writers的样例数据直接跑大概率返回空集不算报错属于业务数据没补齐。5. 常见问题与排查从原始SQL里挖出的五个坑5.1 char字段定长补空格查询和比较都在这里翻车现象用where isbnA0001查得到但where isbn like A0001%查出多条或者从视图里复制isbn作为条件时怎么都匹配不上。原因char(20)是定长类型存A0001时自动在右侧补15个空格。Oracle比较char和varchar2时做了空白填充某些场景下看似正常一旦和其他字符拼接或者做客户端的等值比较空格就会暴露。解决新项目建议把业务编码字段都改成varchar2(20)。这份文档回归时可以先执行trim(isbn)或者直接重建表用varchar2。如果你必须按原文档复现至少查询时统一写rtrim(isbn)。5.2 函数名count撞上Oracle内置名建完就不能正常调用现象按原文档执行create or replace function count可能报ORA-00903或ORA-00955或者调用时分不清是函数还是聚合函数。原因count是SQL关键字/内建聚合函数对象名冲突导致歧义不同Oracle版本对这类命名的容忍度还不一样。解决改成count_author或count_books避免使用系统函数名作为自定义对象名。这个习惯不只是对countlength、sum、nvl这些常用函数名也不要拿去当表名或过程名。5.3 插入Users表时字段和值错位第4个数字把Birthday污染了现象照抄原文档执行用户插入语句可能出现ORA-01861“文字与格式字符串不匹配”或者数据查出来后生日和权限列内容对不上。原因原文档里有的insert列名顺序是UserName,UserPwd,UserNo, Birth,QuanXian,Email,TEL,Address但values里某些行多放了一个数字导致数字被塞进BirthTO_DATE结果被塞进QuanXianOracle要么报错要么默默转换出脏数据。解决插入前仔细数列和值数量date列必须写TO_DATE转换。我建议改成显式列清单避免values和列顺序不一致insert into Users(UserName, UserPwd, UserNo, Birth, QuanXian, Email, TEL, Address) values(冯美, 123, 1, TO_DATE(1986-09-01,YYYY-MM-DD), 1, 530347830qq.com, 13550399250, hubei);列清单明确后就算values里的顺序和表结构不同也不会错位。5.4 SQL语句里的中文弯引号ORA-00933查到怀疑人生现象从Word文档直接复制update语句到SQL*Plus执行报ORA-00933或ORA-01756但语句看起来完全没问题。原因文档编辑器把英文单引号自动替换成了中文弯引号’Oracle不认这个字符字符串没正常结束就直接语法报错。解决写完SQL后检查引号是不是半角。我习惯在SQL*Plus里先show errors或者在本地编辑器里把中文标点高亮出来。原文档的update语句里出现过这个情况照抄前一定要全选替换成英文标点。5.5 表空间文件路径不存在或文件已存在Oracle连库都起不来现象执行create tablespace时报ORA-01119“创建数据文件时出错”或ORA-01537“文件已存在”。原因原文档写死的是D:\oracle\product\10.2.0\oradata\orcl\data03.dbf每台机器Oracle安装路径不同另外如果该路径已经有同名文件而你没用reuseOracle拒绝覆盖。解决先确认自己的数据文件目录比如Linux下的/u01/app/oracle/oradata/orcl/Windows下的D:\app\oracle\oradata\orcl\用v$datafile查看已有文件路径-- 查看当前数据文件位置 select name from v$datafile;把表空间语句里的datafile路径改成自己的目录并保留reuse关键字就能避开这个坑。我一般还会在创建后查一下-- 确认表空间存在 select tablespace_name, status from dba_tablespaces where tablespace_name DATA;6. 验证与进阶把整套设计跑通后再加一个借阅排行榜和分页查询拿到这份课程设计我最建议你做的第一件事不是优化而是按顺序完整跑一遍表空间、建表、索引、视图、序列、存储过程、触发器再插入样例数据。只有全流程通了后续加功能才踏实。验证时可以用下面这个清单自测。验证对象执行方式预期结果表空间查询dba_tablespacesDATA状态为ONLINE基表查询user_tables八个表对象存在索引查询user_indexesBooks和Users有对应索引视图select * from cx_anni返回安妮宝贝图书存储过程call BooksAdd成功插入新书触发器delete一条Books记录Copies对应记录被删样例数据select count(*) from users返回用户总数跑通之后可以顺手把系统需求里的“借阅排行榜”补上。假设Borrow表里有copyno和读者编号按图书统计借阅次数-- 借阅排行榜按图书分组统计借阅次数 select b.title, count(*) as borrow_times from Books b join Copies c on b.isbn c.isbn join Borrow r on c.copyno r.copyno group by b.title order by borrow_times desc;count(*)统计的是Borrow里每条借阅记录group by title把同一本书的多次借阅合并order by borrow_times desc让最多的排在最前。如果想取前5名Oracle 10g没有MySQL的limit要用rownum包一层-- 取借阅次数最多的前5本书 select * from ( select b.title, count(*) as borrow_times from Books b join Copies c on b.isbn c.isbn join Borrow r on c.copyno r.copyno group by b.title order by borrow_times desc ) where rownum 5;分页查询也常用这个思路。Oracle经典分页是先排序再截取不能直接对原始查询用rownum否则取到的行不是真正的前N条。等你把排行榜和分页都跑通这份课程设计就不只是“设计”了而是真正能演示的完整项目。拿到任何课程设计资料我现在的习惯都是先建一个测试账号把DDL到DML按顺序完整跑一遍再谈优化和扩展。数据库这东西很多坑不亲手踩一遍光看文档根本意识不到这份Oracle图书管理系统就是一个很好的练手样本希望帮到你。本文还有配套的精品资源点击获取