目录
1 系统分析
1.1 题目要求
1.2 题目分析
2 数据库设计与实现
2.1 概念结构设计
2.2 物理结构设计
3 数据库实施
3.1 建立数据表、索引、序列,视图
3.2 插入初始数据
3.3 设计pl存储过程
4 其它说明
4.1 遇到的问题
4.2 代码
校园卡可以用于食堂就餐和商铺消费结算。每个食堂消费终端内置有唯一的终端编号,结算时先输入金额,顾客再刷卡,系统自动从卡中扣除相应金额。每个商铺销售终端都有唯一编号,消费时需要输入各商品编号、数量,系统从数据库中读出单价并计算消费总额。每周由财务处进行一次集中结算,统计每个商铺和食堂窗口的销售额。每个食堂窗口和商铺均有多个消费结算终端,均连接到数据库服务器。客户需要先到财务处预存款才可消费。
通过对上述题目要求的分析,总结出一个完整的校园卡消费系统需要具备:存现、转账、退款、商铺消费、窗口消费、挂失、学生信息查询、学生所有订单查询、终端消费统计等功能。
在对现实生活中校园卡消费终端的研究发现,每个终端都内置一个终端编号,每张校园卡也内置一个校园卡卡号(不是学号),比如在消费、存现的过程中,们只需要输入金额,在卡片接触终端机器时,系统会自动识别两端的编号。但在我们模拟过程中,做不到自动识别,所以需要手动的输入终端编号和校园卡卡号,然后通过卡号去查询所属学号,产生一个与学号相关的订单记录并插入到总数据库中。这样即便是更换新的校园卡时,仍然可以查询到消费记录。
但挂失就不一样了,挂失是在我们不知道卡号的情况下,需要用学号到办理中心进行卡号变更,所以输入的是学号。
表结构:
校园卡信息表card:
终端信息表terminal:
商品信息表goods:
消费记录表consume:
变更记录表change:
E-R图:
数据库的物理设计就是根据所选用的DBMS和处理需求,进行物理存储安排,建立索引,形成数据库的内模式, 为逻辑数据模型选取一个最适合应用要求的物理结构的过程, 在这个阶段中要完成两大任务:
1)确定数据库的物理结构,在关系数据库中主要是存取方法和存储结构;
2)对物理结构进行评价,评价的重点是时间和空间效率。
为数据库中各基本表建立的索引如下:
由于基本表 card的属性user_id经常在查询条件和连接操作的连接条件中出现,考虑在这个属性上建立索引。
建立5个数据表,索引为校园卡信息表中的学号属性,并分别在校园卡卡号,消费订单编号,业务办理编号上建立序列。当办理挂失业务和产生订单时,自动生成新的唯一编号。视图为财务部统计的各终端消费汇总情况,按消费金额排名。
录入终端信息,校园卡信息,以及商品信息,订单信息由存储过程自动生成。
1.商铺消费 EXECUTE sp_sql(卡号,终端编号,商品编号,商品数量);
输入校园卡编号,终端编号,商品编号,商品数量,订单号由序列自动创建、时间为当前系统时间。首先判断是否挂失,如果挂失则输出“已冻结”,如图
否则根据商品编号查找商品表goods获取商品价格,乘以数量计算订单金额,查询用户余额,如果余额不足则输出
满足即可创建订单,查寻校园卡信息表获取用户学号,插入一条新的消费记录,更改校园卡余额,获取用户姓名。根据终端编号查询终端名,最后由程序输出本次的记录,类似于小票
整个过程将4个表联系在了一起,需要注意的是创建的订单里面是学号,不是卡号便于后期查询个人消费记录。
2. 食堂消费 EXECUTE st_sql(卡号,终端编号,金额);
同上,只是没有商品数量字段,产生的订单格式一样。
3. 存现程序 EXECUTE cx_sql(卡号,金额);
输入卡号、存现金额。首先查询校园卡信息表判断是否挂失,挂失则输出校园卡已冻结,否则更改余额,并产生业务办理记录,如(交易单号、根据卡号查询的学号、默认的业务类型“存现”、存现金额、自动生成的交易时间),交易单号由序列生成。然后输出业务办理的小票
4. 退款程序 EXECUTE tk_sql(卡号);
输入卡号、首先判断是否挂失,如果未挂失则将校园卡余额更新为0,产生并插入一条业务办理记录,如(交易单号、根据卡号查询的学号、默认的业务类型“退款”、退款金额、自动生成的交易时间)。最后输出业务业务办理的小票
5.转账程序 EXECUTE zz_sql(卡号1,卡号2,金额);
输入卡号1,卡号2,转账金额。首先判断两个卡号是否挂失,然后判断转出账户金额是否充足,若条件都满足则扣减卡号1账户余额,增添卡号2账户余额,并产生业务办理记录(交易单号、根据卡号1查询的学号、默认的业务类型“转账”、转账金额、自动生成的交易时间)。最后打印小票
6.挂失程序 EXECUTE gs_sql(学号);
输入学号,查询到当前使用的校园卡卡号、为其添加“挂失”字段,然后创建新的校园卡表记录,将学号、名字、金额转移(原金额设为0),挂失字段为空。
7.查询某个账户信息 EXECUTE user_sql(学号);
输入学号,查询校园卡信息表,获得当前正在使用的卡号、姓名、余额,并打印输出
8.查询某个用户所有订单信息 EXECUTE user_order(学号,:data); PRINT data;
输入学号,查询消费记录表,获取金额信息,将类型设置为“消费”,根据存储的终端编号查找终端信息表获得终端名,获得订单创建的时间戳。在根据学号查询变更记录表,获取金额信息,将类型设置为存储的类型{“存现”,“转账”,“退款”},地点字段为空,获得记录创建时间。然后将两个查询的结果表纵向连接,按时间先后排序,输出的一张表,这里用到了游标的知识,表的属性为(金额、类型(存现、转账、退款、消费)、地点(只有消费显示地点)、具体时间),结果如下
9.财务部查看各终端消费视图 select * from v_rank;
按消费金额降序排序,统计各个终端的消费情况,比如输出的字段(终端名、所在区域、累计消费金额)。
1.PL程序中序列的引用问题
最开始采用p_change_id:=change_seq.nextval这种形式引用,在oracle11版本上测试成功,但在oracle10版本上出错,最后使用了课本上规范的写法。
SELECT change_seq.nextval
INTO p_change_id
FROM dual;
2.PL程序中多条件查询要使用括号
要根据学号查询校园卡信息表中正在使用的校园卡,一开始没有在and后面的条件外加括号,使得每次查询都出错,但过程却创建成功,网上找了很多资料也不知道什么原因,最后无意中尝试用括号括起来,成功了。WHERE user_id=p_user_id AND (card_lost is null);
3.如何输入一个参数,经查询返回一个表
一开始考虑到两个方案,一是实现能传入参数的视图,因为视图能输出表,但不能引入参数,没有找到这样应用的实例;二使用存储过程,可以传参但只能一行一行的输出,虽然使用for loop循环,但破坏了表的输出形式,经查询,网上都是使用包和游标,太复杂了就没有采用。后来随意尝试发现不用包只用游标也可以,用游标记录一个结果集,通过out传参。存储过程内定义为data out sys_refcursor;过程外定义变量为VARIABLE data refcursor;打印输出print data;
有关此问题看:https://blog.csdn.net/qq_42748385/article/details/103653710
drop table card;
drop table terminal;
drop table goods;
drop table consume;
drop table change;
DROP SEQUENCE card_seq;
DROP SEQUENCE consume_seq;
DROP SEQUENCE change_seq;
drop procedure gs_sql;
drop procedure cx_sql;
drop procedure tk_sql;
drop procedure zz_sql;
drop procedure st_sql;
drop procedure sp_sql;
drop procedure user_sql;
drop procedure user_order;
drop view v_rank;
--建表
create table card(
card_id varchar2(10) primary key,
user_id varchar2(10) not null,
user_name varchar2(10) not null,
card_account float not null,
card_lost varchar2(10)check(card_lost='挂失')
);
create table terminal(
terminal_id varchar2(10) primary key,
terminal_name varchar2(15) not null,
terminal_area varchar2(10)check(terminal_area='西区'or terminal_area='南区') not null
);
create table goods(
goods_id varchar2(10) primary key,
goods_name varchar2(10) not null,
goods_money float not null
);
create table consume(
consume_id varchar2(10) primary key,
terminal_id varchar2(10) not null,
user_id varchar2(10) not null,
consume_money float not null,
consume_time date not null
);
create table change(
change_id varchar2(10) primary key,
user_id varchar2(10) not null,
change_type varchar2(10)check(change_type='存现'or change_type='退款'or change_type='转账') not null,
change_money float not null,
change_time date not null
);
--索引
CREATE INDEX i_user_id ON card(user_id);
--序列
CREATE SEQUENCE card_seq
INCREMENT BY 1
START WITH 10000
CACHE 500
NOCYCLE;
CREATE SEQUENCE consume_seq
INCREMENT BY 1
START WITH 20000000
CACHE 500
NOCYCLE;
CREATE SEQUENCE change_seq
INCREMENT BY 1
START WITH 30000000
CACHE 500
NOCYCLE;
--插入数据
INSERT INTO card VALUES(card_seq.nextval,'1704011011','张三',100,null);
INSERT INTO card VALUES(card_seq.nextval,'1704011012','李四',1000,null);
INSERT INTO card VALUES(card_seq.nextval,'1704011021','王五',0,'挂失');
INSERT INTO card VALUES(card_seq.nextval,'1704011021','王五',100,null);
INSERT INTO goods VALUES('1','矿泉水',2);
INSERT INTO goods VALUES('2','面包',6);
INSERT INTO goods VALUES('3','饼干',4);
INSERT INTO goods VALUES('4','方便面',5);
INSERT INTO goods VALUES('5','卫生纸',3);
INSERT INTO goods VALUES('6','烤肠',1);
INSERT INTO goods VALUES('7','水壶',55);
INSERT INTO terminal VALUES('100','1食堂商店','西区');
INSERT INTO terminal VALUES('101','1食堂1-1班组','西区');
INSERT INTO terminal VALUES('102','1食堂1-2班组','西区');
INSERT INTO terminal VALUES('103','2食堂商店','西区');
INSERT INTO terminal VALUES('104','2食堂2-1班组','南区');
--存现pl
SET serveroutput ON
SET verify OFF
CREATE OR REPLACE PROCEDURE cx_sql
(p_card_id IN card.card_id%TYPE,
p_money IN card.card_account%TYPE)
IS
p_user_id card.user_id%TYPE;
p_user_name card.user_name%TYPE;
p_card_lost card.card_lost%TYPE;
p_change_id change.change_id%TYPE;
BEGIN
SELECT user_id,user_name,card_lost
INTO p_user_id,p_user_name,p_card_lost
FROM card
WHERE card_id=p_card_id;
IF p_card_lost='挂失' THEN
DBMS_OUTPUT.PUT_LINE('存现失败,校园卡已冻结!');
ELSE
BEGIN
UPDATE card SET card_account=card_account+p_money
WHERE card_id=p_card_id;
SELECT change_seq.nextval
INTO p_change_id
FROM dual;
INSERT INTO change VALUES(p_change_id,p_user_id,'存现',p_money,SYSDATE);
DBMS_OUTPUT.PUT_LINE('账户:'||p_user_id||p_user_name||' 存现成功,金额'||p_money||'元,交易编号:'||p_change_id);
END;
END IF;
END;
/
--退款pl
CREATE OR REPLACE PROCEDURE tk_sql
(p_card_id IN card.card_id%TYPE)
IS
p_user_id card.user_id%TYPE;
p_user_name card.user_name%TYPE;
p_card_account card.card_account%TYPE;
p_card_lost card.card_lost%TYPE;
p_change_id change.change_id%TYPE;
BEGIN
SELECT user_id,user_name,card_account,card_lost
INTO p_user_id,p_user_name,p_card_account,p_card_lost
FROM card
WHERE card_id=p_card_id;
IF p_card_lost='挂失' THEN
DBMS_OUTPUT.PUT_LINE('退款失败,校园卡已冻结!');
ELSE
BEGIN
UPDATE card SET card_account=0
WHERE card_id=p_card_id;
SELECT change_seq.nextval
INTO p_change_id
FROM dual;
INSERT INTO change VALUES(p_change_id,p_user_id,'退款',p_card_account,SYSDATE);
DBMS_OUTPUT.PUT_LINE('账户:'||p_user_id||p_user_name||' 退款成功,金额'||p_card_account||'元,交易编号:'||p_change_id);
END;
END IF;
END;
/
--转账pl
CREATE OR REPLACE PROCEDURE zz_sql
( p_card_id1 IN card.card_id%TYPE,
p_card_id2 IN card.card_id%TYPE,
p_money IN card.card_account%TYPE)
IS
p_user_id1 card.user_id%TYPE;
p_user_name1 card.user_name%TYPE;
p_card_lost1 card.card_lost%TYPE;
p_user_id2 card.user_id%TYPE;
p_user_name2 card.user_name%TYPE;
p_card_lost2 card.card_lost%TYPE;
p_change_id change.change_id%TYPE;
p_card_account card.card_account%TYPE;
BEGIN
SELECT user_id,user_name,card_account,card_lost
INTO p_user_id1,p_user_name1,p_card_account,p_card_lost1
FROM card
WHERE card_id=p_card_id1;
SELECT user_id,user_name,card_lost
INTO p_user_id2,p_user_name2,p_card_lost2
FROM card
WHERE card_id=p_card_id2;
IF p_card_lost1='挂失' THEN
DBMS_OUTPUT.PUT_LINE('转账失败,'||p_card_id1||'已冻结!');
ELSIF p_card_lost2='挂失' THEN
DBMS_OUTPUT.PUT_LINE('转账失败,'||p_card_id2||'已冻结!');
ELSE
BEGIN
IF p_card_account