ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

机票预订系统Oracle数据库课设:ER建模、建表与权限备份实践

机票预订系统Oracle数据库课设:ER建模、建表与权限备份实践 简介这是一份基于 Oracle 的机票预订系统数据库课程设计文档面向高校数据库课程设计学生及需要完成类似项目的开发者。内容围绕航空客运业务中的机票预订数据库完整覆盖需求分析、系统 ER 模型、表空间与数据表设计、视图、存储过程、函数、触发器以及系统角色、用户权限与数据备份方案展现了一个大型数据库系统从业务需求到落地实现的全过程。资源共 1 个 Word 文档打包大小约 1.2MB文档目录包含序言、需求分析、分析和设计、课程设计总结等模块并附有关键 SQL 语句实例便于对照学习。目前已有 84 人浏览学习。借助这份材料读者可以清晰理解如何把机票预订业务逐步转化为关系模型掌握 Oracle 中表空间分配、PL/SQL 编程、数据完整性约束和安全管理等实用技能可作为课程设计、期末项目或自学大型数据库开发的重要参考。1. 机票预订系统数据库一个课程设计把ER建模、Oracle建表和权限备份全串起来了数据库课程设计里机票预订系统属于那种看着业务简单、做起来全是细节的题目。航班、机票、乘客、业务员、航空公司这些实体互相引用再加上售票记录稍微不注意主外键就会乱。我拆这份文档时最深的感受是它不是让你随便建几张表交差而是把需求分析、E-R 模型、表空间分配、视图、存储过程、触发器、权限和备份方案都铺开了一遍基本覆盖了 Oracle 数据库课设的全部考点。适合正在做类似课设的学生也适合想补一遍 Oracle 建库完整流程的开发者。你照着改表名、改字段就能用到自己的项目里。2. 需求分析与ER模型从航班、乘客、业务员梳理出7张表2.1 实体与联系谁在订票流程里谁和谁是什么关系拿到题目先别急着写 SQL。文档里的需求分析列得很清楚要管航班基本信息航班号、飞机名称、机舱等级、机票信息票价、折扣、预售状态、经手业务员、客户基本信息姓名、联系方式、证件、付款情况。但真正建表前得把实体找全。原文档梳理出 7 个实体航空公司、飞机、航班、机舱、机票、乘客、业务员。实体之间的关系是一个航空公司有多架飞机和多名业务员一架飞机对应多个航班一个航班有多种机舱等级一个机舱对应多张机票。乘客和业务员通过售票这个联系与机票关联售票联系带一个售票日期属性。这里容易漏的是机舱和航班不是一对一。同一条航线经济舱、商务舱价格和座位数都不一样所以机舱的粒度是航班舱位等级而不是单独一张大表。这一点直接影响后面 cabin 表的主键设计也影响机票表的外键引用。2.2 关系模型与主外键把ER图转成可以建表的范式把 E-R 图转成关系模型后原文档给出的是 7 张表company航空公司、passenger乘客、salesman业务员、airplane飞机、flight航班、cabin机舱、ticket机票外加一张纯关系表 ticketsale售票记录。加起来 8 张。关系模型如下companycnocnamectelcaddresspassengerpIDpnameptelpaddresssalesmansnosIDsnamestelsaddresscnoairplaneanoanamecnoflightfnodeparturearrivaltimeflytimeanocabinfnocblevelseatspricetickettnofnocblevelflydatestatusseatdiscountticketsaletnopIDsnosaledate注意 salesman 里的 sID 是业务员身份证号sno 是业务员编号这是两回事。flight 的 time 是起飞时刻flytime 是飞行时长。cabin 的主键是 (fno, cblevel) 复合主键因为一个航班多个舱位等级。ticket 外键引用了这个复合主键所以 ticket 里必须同时有 fno 和 cblevel才能定位到具体哪趟航班哪个舱位。ticketsale 是典型的三方关联表主键是 (tno, pID, sno)同时记录售出日期。如果你自己设计建议先画清楚谁是主键、谁参考谁。这张表的引用链条是company ← airplane ← flight ← cabin ← ticket ← ticketsale另有两个分支passenger 和 salesman。按这个顺序建表最省事否则先建 ticket 会报外键找不到父表。3. Oracle表空间与建表4个表空间和8张表的PL/SQL落地3.1 表空间划分大数据量表独立放小表共用原文档给出的划分思路很实用乘客表、机票表、售票表数据量大各自单独建表空间其余小表共用一个表空间。这样备份和 I/O 可以分开管理。创建表空间的 SQL 如下CREATE SMALLFILE TABLESPACE PASSENGER DATAFILE F:\APP\ORACLE\ORADATA\ORCL\TICKETSALE\passenger.dbf SIZE 100M AUTOEXTEND ON NEXT 5M MAXSIZE UNLIMITED LOGGING EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO; CREATE SMALLFILE TABLESPACE TICKET DATAFILE F:\APP\ORACLE\ORADATA\ORCL\TICKETSALE\ticket.dbf SIZE 100M AUTOEXTEND ON NEXT 5M MAXSIZE UNLIMITED LOGGING EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO; CREATE SMALLFILE TABLESPACE TICKETSALE DATAFILE F:\APP\ORACLE\ORADATA\ORCL\TICKETSALE\ticketsale.dbf SIZE 100M AUTOEXTEND ON NEXT 5M MAXSIZE UNLIMITED LOGGING EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO; CREATE SMALLFILE TABLESPACE OTHERS DATAFILE F:\APP\ORACLE\ORADATA\ORCL\TICKETSALE\others.dbf SIZE 100M AUTOEXTEND ON NEXT 5M MAXSIZE UNLIMITED LOGGING EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO;这段 SQL 里的关键参数SMALLFILE表示传统小文件表空间一个表空间可以包含多个数据文件AUTOEXTEND ON NEXT 5M表示文件满了自动扩展 5MMAXSIZE UNLIMITED上限不限制实际生产中建议改成具体值比如 2G防止磁盘被意外写满。EXTENT MANAGEMENT LOCAL是本地管理区Oracle 10g 以后默认就是这个不用再管 freelist。我一般会先把数据目录建好比如这里TICKETSALE文件夹要存在否则建表空间会报 ORA-01119 之类的路径错误。这是路径/权限问题后面避坑会细讲。3.2 建表SQL与约束主键、外键、默认值一个不能少文档里的建表 SQL 用了SYSTEM用户实际课设往往也用 SYSTEM 或者自建用户不建议用 SYSTEM但既然原文档是这么写的照做也能跑。建表顺序按引用关系来company 先建然后 salesman、airplane、flight、cabin、ticket、ticketsale。CREATE TABLE SYSTEM.COMPANY ( CNO VARCHAR2(10) NOT NULL, CNAME VARCHAR2(20) NOT NULL, CTEL VARCHAR2(20), CADDRESS VARCHAR2(50), PRIMARY KEY (CNO) VALIDATE ) TABLESPACE OTHERS; CREATE TABLE SYSTEM.PASSENGER ( PID VARCHAR2(20) NOT NULL, PNAME VARCHAR2(20) NOT NULL, PTEL VARCHAR2(20), PADDRESS VARCHAR2(50), PRIMARY KEY (PID) VALIDATE ) TABLESPACE PASSENGER; CREATE TABLE SYSTEM.SALESMAN ( SNO VARCHAR2(10) NOT NULL, SID VARCHAR2(20) NOT NULL, SNAME VARCHAR2(20) NOT NULL, STEL VARCHAR2(20), SADDRESS VARCHAR2(50), CNO VARCHAR2(10) NOT NULL, PRIMARY KEY (SNO) VALIDATE, FOREIGN KEY (CNO) REFERENCES SYSTEM.COMPANY (CNO) VALIDATE ) TABLESPACE OTHERS; CREATE TABLE SYSTEM.AIRPLANE ( ANO VARCHAR2(10) NOT NULL, ANAME VARCHAR2(20) NOT NULL, CNO VARCHAR2(10) NOT NULL, PRIMARY KEY (ANO) VALIDATE, FOREIGN KEY (CNO) REFERENCES SYSTEM.COMPANY (CNO) VALIDATE ) TABLESPACE OTHERS; CREATE TABLE SYSTEM.FLIGHT ( FNO VARCHAR2(10) NOT NULL, DEPARTURE VARCHAR2(20) NOT NULL, ARRIVAL VARCHAR2(20) NOT NULL, TIME DATE NOT NULL, FLYTIME INTERVAL DAY TO SECOND NOT NULL, ANO VARCHAR2(10) NOT NULL, PRIMARY KEY (FNO) VALIDATE, FOREIGN KEY (ANO) REFERENCES SYSTEM.AIRPLANE (ANO) VALIDATE ) TABLESPACE OTHERS; CREATE TABLE SYSTEM.CABIN ( FNO VARCHAR2(10) NOT NULL, CBLEVEL NUMBER(1) NOT NULL, SEATS NUMBER(3) NOT NULL, PRICE NUMBER(5) NOT NULL, PRIMARY KEY (FNO, CBLEVEL) VALIDATE, FOREIGN KEY (FNO) REFERENCES SYSTEM.FLIGHT (FNO) VALIDATE ) TABLESPACE OTHERS; CREATE TABLE SYSTEM.TICKET ( TNO NUMBER(10) NOT NULL, FNO VARCHAR2(10) NOT NULL, CBLEVEL NUMBER(1) NOT NULL, FLYDATE DATE NOT NULL, STATUS NUMBER(1) DEFAULT 1 NOT NULL, SEAT NUMBER(3) NOT NULL, DISCOUNT NUMBER(3, 2) NOT NULL, PRIMARY KEY (TNO) VALIDATE, FOREIGN KEY (FNO, CBLEVEL) REFERENCES SYSTEM.CABIN (FNO, CBLEVEL) VALIDATE ) TABLESPACE TICKET; CREATE TABLE SYSTEM.TICKETSALE ( TNO NUMBER(10) NOT NULL, PID VARCHAR2(20) NOT NULL, SNO VARCHAR2(10) NOT NULL, SALEDATE DATE NOT NULL, PRIMARY KEY (TNO, PID, SNO) VALIDATE, FOREIGN KEY (TNO) REFERENCES SYSTEM.TICKET (TNO) VALIDATE, FOREIGN KEY (PID) REFERENCES SYSTEM.PASSENGER (PID) VALIDATE, FOREIGN KEY (SNO) REFERENCES SYSTEM.SALESMAN (SNO) VALIDATE ) TABLESPACE TICKETSALE;这里有几个细节值得说。TIME字段用 DATE 类型但实际上只存时间插入时用TO_DATE(07-50-00,HH-MI-SS)也能过只是日期部分是当前月首日。更好的做法是用INTERVAL或字符串但课设按文档走没问题。STATUS字段默认 11 表示可售0 表示已售或锁定这个默认值减少了应用层漏填的可能。FLYTIME是INTERVAL DAY TO SECOND配合 TIME 做加法就能得到到达时刻视图里会用到。DISCOUNT 是 NUMBER(3,2)最大 9.99实际折扣 0.7 没问题。PRICE 是 NUMBER(5)最大 99999足够普通票价。TNO 用 NUMBER(10)课设数据量不大但生产环境建议用序列生成不要手工维护。3.3 样本数据填充让查询和视图有东西可看空表没法演示视图和存储过程所以文档里给了 company、salesman、airplane、flight、cabin 的初始数据。我把它们整理成 INSERT 语句方便直接执行。INSERT INTO SYSTEM.COMPANY VALUES (C0001,朝云航空,020-88888888,广东省广州市); INSERT INTO SYSTEM.COMPANY VALUES (C0002,北京航空,010-66666666,北京市); INSERT INTO SYSTEM.COMPANY VALUES (C0003,长沙航空,0731-88888888,湖南省长沙市); INSERT INTO SYSTEM.SALESMAN VALUES (S0001,440902199001011234,邓春国,13911111111,广东省茂名市茂南区,C0001); INSERT INTO SYSTEM.SALESMAN VALUES (S0002,440902199002022345,王军,13922222222,福建省漳州市,C0002); INSERT INTO SYSTEM.SALESMAN VALUES (S0003,440902199003033456,丁磊,13933333333,湖南省邵阳市,C0003); INSERT INTO SYSTEM.SALESMAN VALUES (S0004,440902199004044567,暮云,13944444444,广东省茂名市茂南区,C0001); INSERT INTO SYSTEM.AIRPLANE VALUES (A0001,波音737,C0001); INSERT INTO SYSTEM.AIRPLANE VALUES (A0002,波音777,C0001); INSERT INTO SYSTEM.AIRPLANE VALUES (A0003,波音737,C0002); INSERT INTO SYSTEM.AIRPLANE VALUES (A0004,麦道82,C0003); INSERT INTO SYSTEM.FLIGHT VALUES (F0001,广州,北京,TO_DATE(07-50-00,HH24-MI-SS),INTERVAL 3:30 HOUR TO MINUTE,A0001); INSERT INTO SYSTEM.FLIGHT VALUES (F0002,北京,广州,TO_DATE(12-30-00,HH24-MI-SS),INTERVAL 3:30 HOUR TO MINUTE,A0001); INSERT INTO SYSTEM.FLIGHT VALUES (F0003,广州,长沙,TO_DATE(08-00-00,HH24-MI-SS),INTERVAL 1:05 HOUR TO MINUTE,A0002); -- 其余航班数据按文档表3.4继续插入这里略 INSERT INTO SYSTEM.CABIN VALUES (F0001,1,50,900); INSERT INTO SYSTEM.CABIN VALUES (F0001,2,80,700); INSERT INTO SYSTEM.CABIN VALUES (F0003,1,30,500); INSERT INTO SYSTEM.CABIN VALUES (F0003,2,50,400); INSERT INTO SYSTEM.CABIN VALUES (F0003,3,70,300);插入时最容易翻车的是外键顺序先有 company才有 airplane先有 flight才有 cabin。文档表里 flight 的 TO_CHAR(TIME,HH-MI-SS) 显示的是时间插入时用TO_DATE(07-50-00,HH24-MI-SS)会给一个默认的日期部分不影响视图计算。如果你不想看到乱七八糟的日期可以用TO_DATE(1970-01-01 07:50:00,YYYY-MM-DD HH24:MI:SS)统一基准日期。4. 视图、存储过程与触发器参数化查询、批量录票和状态自检4.1 参数化视图用临时表模拟Oracle视图传参Oracle 原生视图不支持参数但课设要求根据航班号或出发地到达地查询航班信息文档的做法很聪明建一张全局临时表INPUT_TO_FLIGHT作为参数容器视图去 JOIN 这张临时表。应用想要查询时先往临时表里 INSERT 参数再查视图。CREATE GLOBAL TEMPORARY TABLE SYSTEM.INPUT_TO_FLIGHT ( T_FNO VARCHAR2(10), T_DEPARTURE VARCHAR2(20), T_ARRIVAL VARCHAR2(20), T_FLYDATE DATE ) ON COMMIT PRESERVE ROWS; CREATE OR REPLACE VIEW SYSTEM.FLIGHT_VIEW_BYFNO (FNO,CNAME,ANAME,TIME,ARRIVAL_TIME,DEPARTURE,ARRIVAL) AS SELECT fno, cname, aname, time, timeflytime, departure, arrival FROM flight, company, airplane, input_to_flight WHERE flight.ano airplane.ano AND airplane.cno company.cno AND fno input_to_flight.T_fno; CREATE OR REPLACE VIEW SYSTEM.FLIGHT_VIEW_BYSITE (FNO,CNAME,ANAME,TIME,ARRIVAL_TIME,DEPARTURE,ARRIVAL) AS SELECT fno, cname, aname, time, timeflytime, departure, arrival FROM flight, company, airplane, input_to_flight WHERE flight.ano airplane.ano AND airplane.cno company.cno AND departure input_to_flight.T_departure AND arrival input_to_flight.T_arrival;注意ON COMMIT PRESERVE ROWS的含义临时表的数据在事务提交后仍然保留直到会话结束才清空。如果要让每次查询都干净应用里先 TRUNCATE 或 DELETE 临时表再插入新参数。视图的ARRIVAL_TIME用timeflytime计算因为 TIME 是 DATEFLYTIME 是 INTERVAL两者相加返回 DATE正好是到达时间。余票查询视图用到了函数count_ticket我先说视图再讲函数CREATE OR REPLACE VIEW SYSTEM.REMAIN_SEATS_VIEW (FNO,FLYDATE,CBLEVEL,COUNT) AS SELECT DISTINCT fno, flydate, cblevel, count_ticket(fno, flydate, cblevel) FROM ticket, input_to_flight WHERE fno input_to_flight.T_fno AND flydate input_to_flight.T_FLYDATE;这个视图依赖临时表里的 T_FNO 和 T_FLYDATE 两个参数。查询时先插入参数然后 SELECT比如INSERT INTO input_to_flight VALUES(F0003, , , TO_DATE(2025-06-01,YYYY-MM-DD)); SELECT * FROM remain_seats_view ORDER BY cblevel;4.2 存储过程与函数create_ticket批量生成机票、count_ticket算余票机票表数据量大手工 INSERT 不现实。文档里用一张T_NUMBER表保存当前最大机票编号然后存储过程按机舱座位数循环插入机票。这个思路很朴素但很适合课设演示。CREATE TABLE SYSTEM.T_NUMBER ( TNO NUMBER(10) ); INSERT INTO SYSTEM.T_NUMBER VALUES (1); CREATE OR REPLACE PROCEDURE SYSTEM.CREATE_TICKET ( p_fno varchar2, p_flydate date, p_discount number ) AS v_cblevel_count number; v_ticket_count_by_cblevel number; v_tno number; BEGIN SELECT count(1) INTO v_cblevel_count FROM cabin WHERE fno p_fno; SELECT tno INTO v_tno FROM t_number; FOR v_i IN 1..v_cblevel_count LOOP SELECT seats INTO v_ticket_count_by_cblevel FROM cabin WHERE fno p_fno AND cblevel v_i; FOR v_j IN 1..v_ticket_count_by_cblevel LOOP INSERT INTO ticket VALUES(v_tno, p_fno, v_i, p_flydate, 1, v_j, p_discount); v_tno : v_tno 1; END LOOP; END LOOP; UPDATE t_number SET tno v_tno; END;调用方法CALL create_ticket(F0003, TO_DATE(2025-06-10,YYYY-MM-DD), 0.7);存储过程先查 cabin 里这个航班有几个舱位等级再按每个舱位的座位数循环生成对应数量的机票编号从 T_NUMBER 当前值开始递增。这里有个前提cabin 表的 cblevel 必须是从 1 开始连续编号FOR 循环才能对上。如果不连续会报 NO_DATA_FOUND。所以建 cabin 表数据时尽量保证 cblevel 连续。余票函数count_ticket是文档提到但没给完整代码的部分常见做法是这样CREATE OR REPLACE FUNCTION SYSTEM.COUNT_TICKET ( p_fno varchar2, p_flydate date, p_cblevel number ) RETURN number IS v_total number; v_sold number; BEGIN SELECT seats INTO v_total FROM cabin WHERE fno p_fno AND cblevel p_cblevel; SELECT count(1) INTO v_sold FROM ticket WHERE fno p_fno AND flydate p_flydate AND cblevel p_cblevel AND status 0; RETURN v_total - v_sold; END;注意这里status 0表示已售。如果你把 status 的语义定为 1 表示可售那已售判断就要用status 1或者约定 0 为已售。文档里 ticket 表的 STATUS 默认 1含义是当前预售状态所以我们把 0 定位已售、1 定位可售函数里按 status0 统计已售数量。售票过程也可以封装成存储过程一边插入 ticketsale一边更新 ticket.status同时把打印机票需要的信息写到临时表CREATE GLOBAL TEMPORARY TABLE SYSTEM.PRINT_TICKET ( TNO NUMBER(10), FNO VARCHAR2(10), CNAME VARCHAR2(20), ANAME VARCHAR2(20), DEPARTURE VARCHAR2(20), ARRIVAL VARCHAR2(20), FLYDATE DATE, TIME DATE, ARRIVAL_TIME DATE, CBLEVEL NUMBER(1), SEAT NUMBER(3), PRICE NUMBER(5), DISCOUNT NUMBER(3,2), FINAL_PRICE NUMBER, PNAME VARCHAR2(20), PID VARCHAR2(20), SNAME VARCHAR2(20) ); CREATE OR REPLACE PROCEDURE SYSTEM.SALE_TICKET ( p_tno number, p_pid varchar2, p_sno varchar2, p_saledate date ) IS BEGIN -- 插入售票记录 INSERT INTO ticketsale VALUES(p_tno, p_pid, p_sno, p_saledate); -- 更新机票状态为已售 UPDATE ticket SET status 0 WHERE tno p_tno; -- 把票面信息写入临时表供应用层读取打印 INSERT INTO print_ticket SELECT ticket.tno, ticket.fno, cname, aname, departure, arrival, flydate, time, timeflytime, ticket.cblevel, seat, price, discount, price*discount, pname, passenger.pid, sname FROM ticket, flight, airplane, company, passenger, salesman, ticketsale, cabin WHERE ticket.tno p_tno AND ticket.fno flight.fno AND flight.ano airplane.ano AND airplane.cno company.cno AND ticketsale.tno ticket.tno AND ticketsale.pid passenger.pid AND ticketsale.sno salesman.sno AND ticket.fno cabin.fno AND ticket.cblevel cabin.cblevel; END;这里有一个事务原子性的关键点插入售票记录和更新机票状态必须在一个事务里否则会出现票已卖出但状态没改的脏数据。如果你在 PL/SQL 里没有显式 COMMIT调用端可以统一提交。实际课设演示时建议过程内部不加 COMMIT由外层控制这样出错时能整体回滚。4.3 触发器设计售票后自动改TICKET状态文档里提到至少要建 1 个触发器用于数据检查。最常见的做法是在 ticketsale 插入后自动把 ticket.status 改成 0。这样即使应用层忘了更新状态数据库也会兜底。CREATE OR REPLACE TRIGGER SYSTEM.TRG_TICKETSALE_AI AFTER INSERT ON SYSTEM.TICKETSALE FOR EACH ROW DECLARE v_status number; BEGIN -- 先检查机票当前是否可售 SELECT status INTO v_status FROM ticket WHERE tno :NEW.tno; IF v_status 0 THEN RAISE_APPLICATION_ERROR(-20001, 该机票已售出不能重复销售); ELSE UPDATE ticket SET status 0 WHERE tno :NEW.tno; END IF; END;触发器的:NEW.tno是刚插入的售票记录里的机票编号。如果机票已经是状态 0已售直接报业务错误阻止重复售票。这个触发器不仅满足了课设数据检查的要求还顺便解决了并发下重复卖同一张票的问题当然真要防并发得加锁这里课设够用。要注意如果你在 SALE_TICKET 存储过程里已经 UPDATE 了 status再插入 ticketsale 时触发器又来做一次状态检查和更新不会出错只是多一次查询。但如果你在过程里先把 status 改成 0再插 ticketsale触发器查到的就是 0不会误报。顺序是先插 ticketsale 再更新 status 才合理否则触发器会误报已售。5. 权限、备份与常见问题排查安全策略、导出命令和5个坑5.1 角色与用户权限最小授权原则下的SQL数据库安全设计不是把权限都丢给 SYSTEM。文档要求规划角色、用户、权限。常见做法是创建两个角色管理员角色可写普通查询角色只读。然后再创建两个用户分别授予这两个角色。-- 创建角色 CREATE ROLE R_TICKET_ADMIN; CREATE ROLE R_TICKET_QUERY; -- 管理员角色对所有表可增删改查对执行存储过程、函数授权 GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.COMPANY TO R_TICKET_ADMIN; GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.PASSENGER TO R_TICKET_ADMIN; GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.SALESMAN TO R_TICKET_ADMIN; GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.AIRPLANE TO R_TICKET_ADMIN; GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.FLIGHT TO R_TICKET_ADMIN; GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.CABIN TO R_TICKET_ADMIN; GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.TICKET TO R_TICKET_ADMIN; GRANT SELECT, INSERT, UPDATE, DELETE ON SYSTEM.TICKETSALE TO R_TICKET_ADMIN; GRANT EXECUTE ON SYSTEM.CREATE_TICKET TO R_TICKET_ADMIN; GRANT EXECUTE ON SYSTEM.SALE_TICKET TO R_TICKET_ADMIN; GRANT EXECUTE ON SYSTEM.COUNT_TICKET TO R_TICKET_ADMIN; -- 查询角色只读视图和函数 GRANT SELECT ON SYSTEM.FLIGHT_VIEW_BYFNO TO R_TICKET_QUERY; GRANT SELECT ON SYSTEM.FLIGHT_VIEW_BYSITE TO R_TICKET_QUERY; GRANT SELECT ON SYSTEM.REMAIN_SEATS_VIEW TO R_TICKET_QUERY; GRANT SELECT ON SYSTEM.TICKET_INFO_VIEW TO R_TICKET_QUERY; GRANT SELECT ON SYSTEM.SALERECORD_VIEW TO R_TICKET_QUERY; GRANT SELECT ON SYSTEM.SALE_GRADE_VIEW TO R_TICKET_QUERY; GRANT EXECUTE ON SYSTEM.COUNT_TICKET TO R_TICKET_QUERY; -- 创建用户并授予角色 CREATE USER TICKET_USER IDENTIFIED BY ticket123 DEFAULT TABLESPACE OTHERS QUOTA UNLIMITED ON OTHERS; GRANT R_TICKET_QUERY TO TICKET_USER; CREATE USER TICKET_ADMIN IDENTIFIED BY admin123 DEFAULT TABLESPACE OTHERS QUOTA UNLIMITED ON OTHERS; GRANT R_TICKET_ADMIN TO TICKET_ADMIN;这里用到的是传统的对象级授权。如果你用的是 Oracle 12c 以后的版本还要注意CREATE USER前可能需要ALTER SESSION SET CONTAINER之类的操作PDB 环境不同。课设一般用 XE 或单实例直接跑没问题。注意把权限授予角色后再授予用户比直接授予用户更好维护。如果以后新增一张表只需要把新表权限加给角色所有持有该角色的用户自动获得权限不用逐个用户补授权。5.2 备份方案根据表容量定全备增量备文档要求估计表容量并指定备份方案。乘客表、机票表、售票表数据量大单独表空间其他小表共用 OTHERS。备份策略建议数据量小每周日做全库导出周一到周六做增量导出。Oracle 11g 以后推荐用数据泵expdp。# 全库备份使用数据泵 expdp system/oracleorcl schemassystem directoryDATA_PUMP_DIR dumpfileticket_full_%U.dmp logfileticket_full.log fully # 按表空间备份关键数据 expdp system/oracleorcl tablespacePASSENGER,TICKET,TICKETSALE directoryDATA_PUMP_DIR dumpfileticket_big.dmp logfileticket_big.log # 使用 RMAN 做增量备份如果配置了归档模式 rman target / BACKUP INCREMENTAL LEVEL 0 DATABASE PLUS ARCHIVELOG DELETE INPUT; BACKUP INCREMENTAL LEVEL 1 DATABASE PLUS ARCHIVELOG DELETE INPUT;expdp的schemassystem会把 SYSTEM 用户下的所有对象都导出课设够用。dumpfileticket_full_%U.dmp中的%U表示文件太大时可以生成多个分片。如果你的环境没有配置DATA_PUMP_DIR可以先用CREATE DIRECTORY DATA_PUMP_DIR AS F:\BACKUP创建目录然后GRANT READ, WRITE ON DIRECTORY DATA_PUMP_DIR TO SYSTEM;。RMAN 增量备份要求数据库处于归档模式。课设环境如果不是归档模式PLUS ARCHIVELOG会报错。稳妥做法是不强制 RMAN直接用expdp做逻辑备份恢复时impdp导入即可。下面的命令是恢复示例impdp system/oracleorcl schemassystem directoryDATA_PUMP_DIR dumpfileticket_full_01.dmp logfilerestore.log5.3 常见问题排查建表到跑通最容易翻车的点现象 1创建表空间时报 ORA-01119 或 ORA-27040。原因数据文件路径指向的目录不存在或者 Oracle 进程没有该目录的写权限。 解决先确认F:\APP\ORACLE\ORADATA\ORCL\TICKETSALE文件夹存在不存在就手动创建或者在 Oracle 中先CREATE DIRECTORY并授权再用相对路径建表空间。现象 2建表时报 ORA-00955名称已由现有对象使用。原因SYSTEM 模式下已经有同名表可能是上一次课设没清理干净。 解决建表前先查是否存在或者直接执行DROP TABLE SYSTEM.TICKET CASCADE CONSTRAINTS;把依赖它的外键一起删掉。视图、存储过程同理可用DROP VIEW ...、DROP PROCEDURE ...。注意表可以用CREATE OR REPLACE的只有视图和过程表不行。现象 3插入 flight 数据时TIME 字段明明只想存时间查出来却带日期。原因Oracle 的 DATE 类型本身就包含日期和时间TO_DATE(07-50-00,HH24-MI-SS)会默认补当前月的第一天。 解决如果不关心日期部分视图计算和排序不出错就行如果想统一所有 TIME 插入都用TO_DATE(1970-01-01 07:50:00,YYYY-MM-DD HH24:MI:SS)这样日期部分固定展示时用TO_CHAR只取时间。现象 4调用 CREATE_TICKET 时报 NO_DATA_FOUND。原因存储过程用FOR v_i IN 1..v_cblevel_count循环但 cabin 表的 cblevel 不连续比如只有等级 1 和 3循环到 2 时SELECT seats FROM cabin WHERE fnop_fno AND cblevel2查不到数据。 解决保证每个航班的舱位等级从 1 开始连续编号或者把 FOR 循环改成按游标遍历实际存在的 cblevel。我习惯用游标这样即使等级编号有跳跃也不会错。现象 5视图中明明 JOIN 了临时表查出来却为空。原因INPUT_TO_FLIGHT临时表没有插入参数或者插入后因为事务提交被清空了。 解决先INSERT INTO input_to_flight VALUES(...)再SELECT * FROM flight_view_bysite;。如果用了ON COMMIT PRESERVE ROWS记住插入后不要马上 COMMIT否则数据没了。这条是我自己踩过的印象很深。6. 验证与进阶用几条SQL验证整个库再扩展出航段查询课设交差前一定要自己验证一遍数据完整性。我通常按下面三条 SQL 过一遍先看视图能不能查到正确的航班再查余票视图和实际 ticket 表的已售数对不对上最后用 SALE_GRADE_VIEW 核对业务员的销售总额。-- 1. 验证航班视图参数生效 INSERT INTO input_to_flight VALUES(F0001,,,NULL); SELECT * FROM flight_view_byfno; -- 2. 验证余票数 舱位座位数 - 已售票数 SELECT f.fno, c.cblevel, c.seats, (SELECT count(*) FROM ticket t WHERE t.fno c.fno AND t.cblevel c.cblevel AND t.status 0) AS sold, c.seats - (SELECT count(*) FROM ticket t WHERE t.fno c.fno AND t.cblevel c.cblevel AND t.status 0) AS remain FROM cabin c, flight f WHERE c.fno f.fno; -- 3. 核对销售总额视图和明细一致 SELECT sno, sname, cname, SUM(price*discount) sum_sales FROM ticketsale, salesman, company, ticket, cabin WHERE salesman.sno ticketsale.sno AND company.cno salesman.cno AND ticket.tno ticketsale.tno AND cabin.fno ticket.fno AND cabin.cblevel ticket.cblevel GROUP BY sno, sname, cname ORDER BY sno;如果第 2 条查出来的 remain 和 REMAIN_SEATS_VIEW 的 COUNT 不一致说明 TICKET 状态更新有遗漏或者触发器没生效。这时候回查 STATUS 字段SELECT status, count(*) FROM ticket GROUP BY status;正常应该只有 1可售和 0已售两种。关于进阶我建议在现有模型上加一个航段查询的体验把 flight_view_bysite 改成支持城市模糊匹配比如用户在界面输入北京就能查出所有从北京出发的航班。实现上不用改视图直接在应用层拼接 SQL 时把 T_DEPARTURE 改成LIKE %北京%视图里用departure LIKE input_to_flight.T_departure || %即可。虽然 Oracle 视图传参麻烦但通过临时表拼接 LIKE 是常见做法。再进一步可以考虑把 T_NUMBER 换成 Oracle 序列CREATE SEQUENCE ticket_seq START WITH 1 INCREMENT BY 1;然后在 CREATE_TICKET 里用ticket_seq.NEXTVAL生成机票编号。这样比维护一张表更抗并发也不会出现两个人同时取出同一个编号的尴尬。另外如果想把这套模型做得更贴近生产可以加一张退票记录表记录退票操作同时把 ticket.status 改回 1。这个表可以做成触发器日志的简单版本AFTER DELETE ON ticketsale 时将 ticket.status 重置为 1。不过要注意退票后原座位是否可重新售卖业务上需要判断别简单恢复就完事。从那以后我每次做完 Oracle 课设都会强制走一遍上面三条验证 SQL再顺手把关键表的增删改查各跑一遍。这个习惯帮我省了不少答辩现场改数据的尴尬。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

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