ATM取款机的数据库模拟开发和实战总结

一.ATM实战开发的简介。

 学习了几天的Oracle,开始着手用数据库PL/SQL语言做一个简单的ATM取款机业务,主要是为了巩固数据库的知识,并非真正的去实现高端的业务。有兴趣的可以看看,希望对同胞们都有用。

  •  ATM的表。它有四张表,用户信息表userinfo,卡号信息表cardinfo,交易信息表tradeinfo,存款类型表deposit。
  用户信息表userInfo
customerID int 客户编号 主键
customerName varchar(10) 用户名 not null
personID varcahr(20) 身份证号

not null unique 只能是15位

或者18位符合实际的身份证号

telephone varcahr(20) 联系电话

not null,格式为xxxx-xxxxxxxx或者xxx-xxxxxxxx

或者11手机号

address varchar(30) 居住地址 可选
--创建userinfo表--
create table UserInfo(
customerID int primary key,
customerName varchar(10) not null,
personID varchar(20) not null unique,
telephone varchar(20) not null,
address varchar(30)
);
--为身份证号和电话增加正则表达式约束--
alter table userinfo add constraint CK_TELEPHONE
check(regexp_like(telephone,'^1[3,4,5,7,8][0-9]-[0-9]{8}$|^[0-9]{3,4}-[0-9]{8}$'));
alter table userinfo add constraint CK_PERSONID
check(regexp_like(personid,'^[0-9]{15}$|^[0-9]{17}[0-9,x]$'));
卡号信息表cardInfo
cardID varchar(30) 卡号

主键,如1010 3576 xxxx xxxx,

每4位后面有空格,卡号随机产生。

curID varchar(5) 货币种类 必填,默认为RMB
savingID varchar(5) 存款类型 外键,必填。
openDate date 开户日期 必填。默认为当前时间
openMoney decimal(10,2) 开户金额 必填,不低于1.
balance decimal(10,2) 余额 必填,不低于1.
pwd varchar(10) 密码 必填,6位数字。默认为888888
isReportLoss char(2) 是否挂失 必填,只能是'是'或'否'。默认为'否'

  customerid    int    开户编号        外键,必填。

--创建cardinfo表--
create table CardInfo(
cardID varchar(30) primary key,
curID varchar(5) default 'RMB' not null,
savingID varchar(5) not null,
openDate date default sysdate not null,
openMoney decimal(10,2) not null check(openMoney>=1),
balance decimal(10,2) not null check(balance>=1),
pwd varchar(10) default '888888' not null ,
IsReportLoss char(2) default '否' not null,
customerID int not null references UserInfo(customerID)
);
--为卡号和密码增加正则表达式约束--
alter table cardinfo add constraint CK_PWD check
(regexp_like(pwd,'^[0-9]{6}$')); alter table cardinfo add constraint CK_CARDID check
(regexp_like(cardid,'^1010[[:space:]]3576[[:space:]][0-9]{4}[[:space:]][0-9]{4}$'));
交易信息表tradeInfo
transdate date 交易日期 必填。默认为系统时间
cardID varchar(30) 卡号 外键,必填。
transType varchar(10) 交易类型

必填,只能是存入或者支取

transmoney decimal(10,2) 交易金额 必填,大于0
remark varchar(50) 备注 可选
--创建tradeinfo表--
create table TradeInfo(
transDate date default sysdate not null,
cardID varchar(30) not null references CardInfo(cardID),
transType varchar(10) not null,
transMoney decimal(10,2) not null,
remark varchar(50)
);

  --为transtype增加约束--
  alter table tradeinfo add constraint CK_TRANSTYPE check (transtype in('支取','存入'));

存款类型表deposit
savingID int 类型编号 主键 
savingName varchar(20) 存款类型名称 not null unique
  • ATM模拟实现的业务。

   1.修改密码。

   2.挂失。

   3.查询本周开户的卡号。

   4.查询本月一次性交易金额最高的卡号。

   5.查询卡号挂失的用户的信息。

   6.开户。

   7.存款或者取款。

二.插入数据。

  •    为deposit表插入数据。

   插入数据前我们应该有这样一个认识,与客户无关的信息表先插入数据,与客户有关的次之。因为你开户的时候,客户存款类型必须与存款类型表deposit表的一条记录匹配才行。这就像一个银行一样,不管有多少客户,你银行本身的数据以及功能是必须有的。所以,先插入与银行的信息有关与客户无关的表的数据。

   为deposit插入数据的时候,存款类型编号我们一般也不会自己去输入,所以要创建一个插入类型名称时,自动插入类型编号的触发器。

    首先创建一个savingid的序列。

--创建savingid序列--
create sequence savingid_incr --创建序列不能加or replace
start with 1
increment by 1
nomaxvalue
nocycle
cache 30;

    然后创建savingid的触发器。

--创建savingid的触发器--
create or replace trigger savingid_insert
before insert on deposit
for each row
declare
begin
:new.savingid:=savingid_incr.nextval;
end;

    现在就可以插入数据了。

insert into deposit(savingname) values('定期');
insert into deposit(savingname) values('活期期');
insert into deposit(savingname) values('定活两便');

    检测数据。select * from deposit;可以看到三条savingid自动产生并插入的记录。

  •    为userinfo表和cardinfo表插入数据:

     对于userinfo表,在插入数据的时候,我们不可能每次都去插入客户编号,所以要为客户编号创建一个插入其他数据时的客户编号自动插入的触发器;还要为卡号创建一个随机产生的过程,并且开户的时候通过过程去插入,因为卡号是随机产生并且不能重复,所以我把这个判断写入了开户的业务逻辑中。

     在这里的话,我直接去实现开户,然后通过开户为userinfo表和cardinfo表插入数据,东西很多,我们一步一步来。

     先为customerid创建序列。

create  sequence id_incr --创建序列不能加or replace
start with 1001
increment by 1
nomaxvalue
nocycle
cache 30;

     再为customerid创键触发器。

create or replace trigger id_insert
before insert on userinfo
for each row
declare
next_customerid userinfo.customerid%type;
begin
:new.customerid:=id_incr.nextval;
end;

    为cardid创建随机产生的过程。

create or replace procedure r_cardid(out_id out varchar2)
as
r_num number;
front_id varchar2(4);
back_id varchar2(4);
real_id varchar2(20);
begin
select lpad(trunc(dbms_random.value*100000000),8,0)into r_num from dual;
front_id:=to_char(substr(r_num,1,4));
back_id:=to_char(substr(r_num,5,4));
real_id:='1010 3576 '||front_id||' '||back_id;
out_id:=real_id;
end;

   开户的时候,除了customerid和cardid以及表给的默认值,其他都是与用户开户有关的信息。所以准备工作做好之后,我们就可以实现开户的业务了。

*****************************************************开户******************************************************

******开户的数据*********

  开户时需要用户输入的有:

    身份证号——personid
    存款类型——savingid
    存款金额——openmoney
    本卡密码——pwd
    (在开户时如果用户是第一次开户的话,就又需要的输入的有:)
    姓名——customername
    联系方式——telephone
    地址——address

  系统自动赋予的值有:
    用户号——customerid(触发器触发)
    卡号——cardid(调用r_cardid(outid)过程)

    其他都为系统给的默认值。

******开户的存储过程逻辑******

    1.先判断用户是不是第一个开卡,如果是,那么先创建用户信息,再开户。

    2.用户创建之后或者用户已存在时,调用卡号随机产生的存储过程,产生一个随机产生的不重复的卡号。

    3.卡号产生之后,判断用户输入的存款类型是否正确,正确,就可以开户了。不正确则撤销之前所有的操作并且提示开户终止。

    4.开户就是插入一条符合逻辑的cardinfo记录,并且提示开户的最终信息。

******开户的存储过程************

create or replace procedure openAccount(
temp_personid in userinfo.personid%type,
temp_savingname in deposit.savingname%type,
temp_openmoney in cardinfo.openmoney%type,
temp_pwd in cardinfo.pwd%type,
temp_customername in userinfo.customername%type,
temp_telephone in userinfo.telephone%type,
temp_address in userinfo.address%type)
as
isnullpersonid userinfo.personid%type; --select into判断身份证号是否存在。
temp_cardid cardinfo.cardid%type; --select into判断新产生的卡号是否存在,并且在后来是要用的。
temp_savingid cardinfo.savingid%type;--select into 判断用户输入的存款类型是否可用。
temp_customerid userinfo.customerid%type;
begin
begin
--判断用户是否存在
select personid into isnullpersonid from userinfo where personid=temp_personid;
exception
when no_data_found then
-----创建用户----
insert into userinfo(customername,personid,telephone,address)
values(temp_customername,temp_personid,temp_telephone,temp_address);
end;
begin
while 1=1 loop --产生一个唯一不重复的卡号
r_cardid(temp_cardid);
select cardid into temp_cardid from cardinfo where cardid=temp_cardid;
--如果没有找到,则证明新产生的卡号是唯一的,进入exception继续完成操作。
end loop;
exception
when no_data_found then
--来到这里说明产生的卡号是可用的,接下来就应该判断存款类型temp_savingid.
begin
select savingid into temp_savingid from deposit where savingname=temp_savingname;
--如果存在,那么就可以开户了,如果不存在,则撤销之前的所有操作,用事务。 --customerid是之前就有或者在开户中自动产生的,所以这里要通过SQL找到它。
select customerid into temp_customerid from userinfo where personid=temp_personid;
--开户---
insert into cardinfo(cardid,savingid,openmoney,balance,pwd,customerid)
values(temp_cardid,temp_savingid,temp_openmoney,temp_openmoney,temp_pwd,temp_customerid);
dbms_output.put_line(' 开户成功!');
dbms_output.put_line('您的银行卡号为:'||temp_cardid);
dbms_output.put_line('开户日期:'||sysdate||' 开户金额 '||temp_openmoney);
exception
when no_data_found then
rollback;--撤销之前的所有操作
raise_application_error(-20000, '存款类型不正确,开户终止!');
end;
end;
end;

***************利用开户存储过程来插入数据******************

插入一条用户第一次开户的数据:

set serveroutput on;
execute openAccount(410181199308084016,'定期',50000,762723,'徐万轩','151-03891462','河南省郑州市');

插入一条老用户开户的数据:

set serveroutput on;
execute openAccount(410181199308084016,'活期',50000,762723,'徐万轩','151-03891462','河南省郑州市');

此时查表就会发现有两条cardinfo记录,一条userinfo记录。

  •    为tradeinfo表插入数据。

    tradeinfo表示记录交易信息的,所以我们可以调用存取款的过程来为tradeinfo表插入数据。

*****************************************************存取款存储过程*******************************************

******存取款需要用户的数据**********

  需要用户输入的信息:

    存款或者取款的金额:temp_money
    存款或者取款的类型:temp_transtype
    银行卡号:temp_cardid

******存取款的实现逻辑*************

  1.首先判断用户输入的卡号是否存在,不存在则退出操作。

  2.卡号存在,再判断卡是否挂失,如果挂失,提醒并退出操作。

  3.没有挂失的话,判断存取款类型是否正确,如果正确,就可以存取款了。

  4.存取款时,更新cardinfo表卡的balance余额,并且插入一条交易信息表。

  5.如果取款之后,余额小于1,就会发生检查约束错误,捕获错误并且利用事务的原理rollback之前的操作。

*******存取款的存储过程**************

create or replace procedure inout_money(
temp_money in number,
temp_transtype in tradeinfo.transtype%type,
temp_cardid in cardinfo.cardid%type
)
as
isnulltranstype tradeinfo.transtype%type;--判断存取款类型是否正确
isnullcardid cardinfo.cardid%type;--判断银行卡是否存在
isnullloss cardinfo.isreportloss%type;--判断银行卡是否冻结
begin
begin
--判断卡号是否存在
select cardid into isnullcardid from cardinfo where cardid=temp_cardid;
exception
when no_data_found then
begin
raise_application_error(-20000,'卡号不存在!');
end;
end;
begin
--先判断卡号是否冻结
select isreportloss into isnullloss from cardinfo where cardid=temp_cardid;
if isnullloss='是' then
raise_application_error(-20001,'该卡已冻结,不能执行该操作!');
end if;
--判断存取款类型是否存在
select distinct transtype into isnulltranstype from tradeinfo where transtype=temp_transtype;
if temp_transtype='支取'then
update cardinfo set balance=balance-temp_money where cardid=temp_cardid;
insert into tradeinfo(cardid,transtype,transmoney)
values(temp_cardid,temp_transtype,temp_money);
dbms_output.put_line('取出'||temp_money||'RMB!');
elsif temp_transtype='存入' then
update cardinfo set balance=balance+temp_money where cardid=temp_cardid;
insert into tradeinfo(cardid,transtype,transmoney)
values(temp_cardid,temp_transtype,temp_money);
dbms_output.put_line('存入'||temp_money||'RMB!');
end if;
exception
when no_data_found then
rollback;
raise_application_error(-20002,'存取款类型不正确!');
when others then
dbms_output.put_line('余额不能少于1');
rollback; end;
end;

*************利用存取款存储过程来插入tradeinfo数据*****************

  set serveroutput on;
  execute inout_money(500,'存入','1010 3576 1685 3672');

  set serveroutput on;
  execute inout_money(500,'支取','1010 3576 1685 3672');

  这时,trande表会有两条记录,一个是1010 3576 1685 3672支取的记录,一个是1010 3576 1685 3672的存入记录。

  其实,开户和存取款的过程与插入数据时两回事,但是我们却可以利用这两个过程来实现数据的插入,本人在这里也是懒省事。其实,这样插入数据的话,思路上也比较好理解。毕竟,对于练习来说,我们在实现业务之前的数据只是为了检查这些数据的插入是否满足表的约束和它的合理性,但是,在实际的开发过程中,一个业务的完成的前提条件是你必须有实现这个业务的功能,所以先实现业务,通过业务来插入数据时最为合理的。不过这样对于初学者或者基础不太扎实的同胞可能就有些难以理解了,没事,慢慢来吧。

三.实现业务逻辑。

  • 挂失。

 ********挂失需要用户输入的信息**********

  1.卡号。

  2.密码。

  3.账户ID。

  ******挂失的存储过程*********

create or replace procedure lose(temp_cardid in cardinfo.cardid%type,
temp_pwd in number,temp_customerid in varchar2)
as
row_info cardinfo%rowtype;
islose varchar2(5);
begin
select isreportloss into islose from cardinfo where cardid=temp_cardid;
if islose='是' then
dbms_output.put_line('此卡已经挂失!');
goto last_point;
end if;
select * into row_info from cardinfo
where cardid=temp_cardid;
if row_info.pwd=temp_pwd and row_info.customerid=temp_customerid then
update cardinfo set IsReportLoss='是' where cardid=temp_cardid;
dbms_output.put_line('挂失成功!');
else
dbms_output.put_line('对不起,您输入卡的信息不正确,不能挂失!');
end if;
<<last_point>>
null;
exception
when NO_DATA_FOUND then
dbms_output.put_line('您输入的卡号不存在!');
end;

  **********测试***********

  set serveroutput on;
  execute lose('1010 3576 4654 1134','888866','1001');

  • 修改密码。

  ********修改密码所需的用户的信息********

  1.卡号

  2.密码

  **********修改密码的存储过程**********

create or replace procedure up_pwd(temp_cardid in varchar2,
temp_newpwd in number)
as
temp varchar2(30);
BEGIN
select cardid into temp from cardinfo where cardid=temp_cardid;
update cardinfo set pwd=temp_newpwd where cardid=temp_cardid;
dbms_output.put_line('修改密码成功!');
EXCEPTION
when no_data_found then
dbms_output.put_line('输入的卡号不存在!');
when others then
dbms_output.put_line('违反检查约束');
END;

  *********修改密码的测试*************

    set serveroutput on;
    execute up_pwd('1010 3576 0030 0000','666652');

  • 查询本周开户的卡号。

select * from cardinfo where opendate>=trunc(sysdate,'day');
  • 查询本月一次性交易金额最高的卡号。

select * from tradeinfo;
--from后面跟本月的搜索结果,where处控制transmoney为最大值。
select distinct * from(select * from tradeinfo where transdate>trunc(sysdate,'month'))
where transmoney in( select max(transmoney) from tradeinfo);
  • 查询卡号挂失的用户的信息。

select u.customername,u.customerid,u.personid,u.telephone,u.address
from userinfo u inner join cardinfo c on
c.customerid=u.customerid
where c.isreportloss='是';

  

四.开发中遇到的一些问题。

  在开发的过程中,遇到了许多问题,因为我学习Oracle数据库也就十天左右的时间,对于一些基本的还不是很熟悉。

  遇到的问题:

    1.创建过程的时候不能有declare关键字。

    2.goto流程控制不能goto到exception执行体中。

    3.select into语句千万别返回多行语句,在编译的时候是不会出错的,调用的时候出错也会提示较多的错误,修改很麻烦。比如,查看存款类型是否合法的时候,就需要在select语句前加上distinct来确保返回的是一条语句。

    4.创建序列不可以使用 or replace。

    5.添加行级触发器的时候,赋值用 :new.属性值来赋值。如果select into语句中也赋值的话,就会用两次序列自动产生的值,所以编译器不会报错,但是你的序列的增长率却是你想要的二倍。

    6.在像开户这样的逻辑实现过程中,需要多次去用select去判断。而且它还有判断之后需要共同实现的部分,所以对于我一些新手来说,还是比较难于理解begin end的嵌套结构。bengin  end里面可以嵌套begin end;exception之后的when then也可以接着begin end并且也可以嵌套。

    7.日期函数不是很熟悉。

      tranc(date,day)日期date所在周的周日的日期。

      interval '2' month将2作为month来运算。日期的加减默认是天。

      last_day(date),date日期所在月的最后一天的日期。

      add_months(date,2) date日期两个月之后的日期。

    8.数值函数不是很熟悉。

      dbms_random.value产生0-1之间的小数,精确到很多位。

      lpad(对象,位数,0)向对象左侧填充0,知道对象的位数=输出的位数。

      trunc(数值对象[,截取精度]),如果截取精度>0,则截取到小数点后第几位,如果截取精度<0,则截取到小数点前第几位,截取的部分用0填充。如果截取精度=0,则去掉小数部分。截取精度不写的话默认为0.

Oracle实战训练——ATM取款机业务的更多相关文章

  1. ATM取款机的数据库模拟开发和实战总结

    一.ATM实战开发的简介. 学习了几天的Oracle,开始着手用数据库PL/SQL语言做一个简单的ATM取款机业务,主要是为了巩固数据库的知识,并非真正的去实现高端的业务.有兴趣的可以看看,希望对同胞 ...

  2. 大话JS面向对象之开篇万物皆对象------(ATM取款机引发的深思)

    一,总体概要 OO(面向对象)概念的提出是软件开发工程发展的一次革命,多年来我们借助它使得很多大型应用程序得以顺利实现.如果您还没有掌握并使用OO进行程序设计和开发,那么您无疑还停留在软件开发的石器时 ...

  3. Java软件系统功能设计实战训练视频教程

    Java软件系统功能设计实战训练视频教程 第01节课:整体课程介绍和杂项介绍第02节课:软件功能设计常见理念和方法第03节课:关于软件设计的一些思考第04节课:第一周作业的业务和相应模式:综合应用简单 ...

  4. ATM取款机模拟——数据结构课设

    今天帮人写的第二篇课设 . ;-) 机智的窝 要求:大概说一下吧,就是要创建一个用户(初始化一账户),模拟ATM的业务(取款,100的整数倍,改密               码,查剩余金额.等等,各 ...

  5. 第一周:设计一个简易ATM取款机简易程序(2)

    1.了解用户对ATM取款机功能需求如下: 2.新建一个login函数使用for循环方法和if选择方法编写登陆界面用来及设置ATM内用户的金额和取款机内的金额: 3.使用新建函数方法及if选择方法编写登 ...

  6. 团队Github实战训练

    班级:软件工程1916|W 作业:团队Github实战训练 团队名称:SkyReach Github地址:Github地址 贡献比例表 队员学号 队员姓名 此次活动任务 贡献比例 221600106 ...

  7. 团队作业第六次—团队Github实战训练

    作业描述 课程 软件工程1916|W(福州大学) 团队名称 修!咻咻! 作业要求 团队作业第六次-团队Github实战训练 团队目标 搭建一个相对公平公正的抽奖系统,根据QQ聊天记录,完成从统计参与抽 ...

  8. Oracle实战笔记(第一天)

    导读 笔记内容来自韩顺平老师的视频<玩转Oracle实战教程>,可以结合笔记进行观看.第一天视频中还有Oracle的介绍和安装等内容,很容易搜索到,这里就不再进行总结. 目录 1.命令行工 ...

  9. 团队作业第六次——团队Github实战训练

    作业格式 课程名称:软件工程1916|W(福州大学) 作业要求:团队作业第六次-团队Github实战训练 团队名称:葫芦娃队 作业目标:确定和分析选题,绘制评审表 github地址:https://g ...

随机推荐

  1. jquery ashx交互 返回list 循环json输入信息

    html代码:触发按钮 <input type="button" id="search" value="查询" /> ashx代 ...

  2. Linux常用命令组合

    1.删除除某个文件或文件夹外的所有的内容 ll |grep -v test |xargs rm -rf find . -maxdepth 1 -type d|grep -v test|xargs rm ...

  3. AngularJS 表单

    AngularJS 表单是输入控件的集合. HTML控件 以下 HTML input 元素被称为 HTML 控件: input 元素 select 元素 button 元素 textarea 元素 H ...

  4. jsf初学解决faces 中文输入乱码问题

    中文乱码,貌似在java里很常见,请看简单代码: 很简单的faces <div class="td-block"> <h:outputLabel class=&q ...

  5. C++ 系列:C++ 基础 001

    Copyright © 1900-2016, NORYES, All Rights Reserved. http://www.cnblogs.com/noryes/ 欢迎转载,请保留此版权声明. -- ...

  6. UWP 下拉刷新控件(PullToRefreshControl)

    最近项目里面有下拉刷新的需求,自己做了一个,效果还不错. <Style TargetType="local:PullToRefreshControl"> <Set ...

  7. 【Android】achartengine的柱状图和饼状图的使用

    本文介绍了android中如何使用achartengine绘制饼图和柱状图,请分别尝试饼图和柱状图,曲线图. 先看效果图: 先看看获取数据: Workbook workbook = Workbook. ...

  8. How to print 如何输出 int64_t,uint64_t的值 in C

    For int64_t type: int64_t t; printf("%"PRId64"\n", t); for uint64_t type: uint64 ...

  9. LINUX 查看当前系统的负载情况

    uptime linux uptime命令主要用于获取主机运行时间和查询linux系统负载等信息. eg: # uptime 02:03:50 up 126 days, 12:57, 2 users, ...

  10. git stash和git stash pop

    git stash 可用来暂存当前正在进行的工作, 比如想pull 最新代码, 又不想加新commit, 或者另外一种情况,为了fix 一个紧急的bug,  先stash, 使返回到自己上一个comm ...