Oracle_PL/SQL(4) 过程和函数

create table s_sc (
SNAME VARCHAR2(20) primary key,
c_grade NUMBER(6),
m_grade NUMBER(6),
e_grade NUMBER(6)
);
declare
begin
for rs in (select sno,sname from student) loop
insert into s_sc (sname) values (rs.sname);
for rs2 in (select grade,cname from sc,course
where sc.cno=course.cno and sno=rs.sno) loop
if rs2.cname='语文' then
update s_sc set C_GRADE=rs2.grade where sname=rs.sname;
elsif rs2.cname='数学' then
update s_sc set M_GRADE=rs2.grade where sname=rs.sname;
elsif rs2.cname='英语' then
update s_sc set E_GRADE=rs2.grade where sname=rs.sname;
end if;
end loop;
end loop;
end;

开发子程序:过程和函数
过程:存储过程是用于执行特定操作的PL/SQL块。特定操作主要包括对数据库表的增、删、改、查。
如果系统或用户经常需要执行特定操作,那么可以考虑基于这些操作建立存储过程,不仅可以简化应用的开发和维护,还可以提高应用程序的运行效率。
过程:用于对表的增、删、改、查
函数:用于返回特定数据

1.过程
语法:
create [or replace] procedure proc_name(
argument1 [model] datatype1,arguement2 [mode2],...)
is [as]
变量
begin
语句
end;
关键字解释:
create or replace:create是创建,replace是替换,
create or replace 如果系统中不存在这个存储过程时,则创建一个新的;
如果系统中已经存在同名的存储过程,则将原来的存储过程覆盖或替换掉。
procedure:存储过程的关键字,类似的关键字还有trigger、function、package等。
proc_name:存储过程名称,在同一个用户/模式下唯一,命名规则为:proc_表名_操作名。
参数名:有意义的名字,不能是acd等,一般是列名。
mode:参数模式,包括IN、OUT、INOUT三种类型。
IN仅作为输入参数使用,OUT仅作为输出参数使用,INOUT即是输入参数又是输出参数。
is …begin间:是定义变量的区域。
begin … end间:是存储过程的主体区域,用来完成存储过程的功能。

1.1 建立过程:不带任何参数
第一个存储过程,无参数部分、无变量、语句部分只有null
create or replace procedure proc_null
is
begin
null;
end proc_null;
不能一句语句都没有
不能重名

练习:构造无参存储过程打印当前日期
例1:
create or replace procedure proc_sysdate is
begin
--dbms_session.set_nls('nls_date_format','"yyyy-mm-dd hh24:mi:ss"');
dbms_output.put_line(sysdate);
end proc_sysdate;
/
show error;

1.2 调用过程
Command窗口调用
set serveroutput on;
exec proc_sysdate;
execute proc_sysdate;
SQL窗口、Test窗口
begin
proc_sysdate();
end;
总结:
存储过程可以被pl/sql块(包括:触发器、存储过程、包、函数、job)直接调用;
也可以被sql通过执行命令execute(缩写exec)调用;
ORM框架(mybais、ibatis、hibenate)及JDBC将sql语句作为字符串进行传递,
所以sql中仍可以通过execute执行存储过程。

1.3 建立过程:带有IN参数
例2:
create or replace procedure add_employee
(p_eno number,p_name varchar2,p_sal number,
p_job varchar2 default 'clerk',p_dno number)
is
begin
insert into emp(empno,ename,sal,job,deptno) values(p_eno,p_name,p_sal,p_job,p_dno);
end;
/
show err;
exec add_employee(1111,'clark',2000,'manager',10);
练习:编写proc_dept_add存储过程
create or replace procedure proc_dept_add
(p_deptno number,p_dname varchar2,p_loc varchar2)
is
begin
insert into dept(deptno,dname,loc) values(p_deptno,p_dname,p_loc);
end;
/
show err
1.4 建立过程:带有OUT参数
例3:
create or replace procedure proc_employee
(p_eno number,p_name out varchar2,p_salary out number)
is
begin
select ename,sal into p_name,p_salary from emp where empno=p_eno;
end;
/
show err;

当在应用程序中调用该过程时,必须要定义变量接受输出参数的数据
sql>variable name varchar2(10);
var salary number;
exec proc_employee(7788,:name,:salary);

declare
v_name varchar2(100);
v_salary emp.sal%type;
begin
proc_employee(7788,v_name,v_salary);
dbms_output.put_line(v_name||' '||v_salary);
end;

1.5 建立过程:带有IN OUT参数(输入输出参数)
例4:
create or replace procedure proc_compute
(p_num1 in out number,p_num2 in out number)
is
v1 number;
v2 number;
begin
v1:=trunc(p_num1/p_num2);
v2:=mod(p_num1,p_num2);
p_num1:=v1;
p_num2:=v2;
end;
/
show err;

sql>variable n1 number
var n2 number
exec :n1:=100
exec :n2:=30
exec proc_compute(:n1,:n2)

declare
v_num1 number:=100;
v_num2 number:=30;
begin
proc_compute(v_num1,v_num2);
dbms_output.put_line('商:'||v_num1||' 余数:'||v_num2);
end;

1.6 为参数传递变量和数据
位置传递,名称传递,组合传递三种
位置传递:在调用子程序时按照参数定义的顺序为参数指定相应的变量或数值
exec proc_dept_add(41,'开发部','北京');
--exec proc_dept_add(42);
exec proc_dept_add(42,null,null);
create or replace procedure proc_dept_add
(p_deptno number,p_dname varchar2 default '',p_loc varchar2 default '')
is
begin
insert into dept(deptno,dname,loc) values(p_deptno,p_dname,p_loc);
end;
/
show err;
exec proc_dept_add(43);
名称传递:在调用子程序时指定参数名,并使用关联符号=>为其提供相应的数值或变量
exec proc_dept_add(p_dname=>'售后部',p_deptno=>44);
exec proc_dept_add(p_deptno=>45);
组合传递:同时使用位置传递和名称传递
exec proc_dept_add(46,p_loc=>'深圳');
exec proc_dept_add(47,p_dname=>'人事部',p_loc=>'上海');

1.7 查看过程原代码
oracle会将过程名,源代码以及其执行代码存放到数据字典中.执行时直接按照其执行代码执行
可查询数据字典(user_source)
select text from user_source where lower(name)='proc_dept_add';

1.8 删除过程
drop procedure proc_name;

1.9 编译存储过程
alter procedure proc_name compile;
--alter table account add mark varchar2(100); 更新表结构会导致存储过程失效

练习:
转账业务
create table account(
accountid number(16),
name varchar2(20),
value number(16,2));
insert into account values(111,'acdd',10000);
insert into account values(112,'acdd1',100);

create or replace procedure proc_trans_monery
(p_account_out number,p_account_in number,p_value number)
is
begin
Update account set value= value-p_value where accountid= p_account_out;
Update account set value= value+p_value where accountid= p_account_in;
end;
exec proc_trans_monery(111,112,1000);

做到这步基本功能是完成,但是代码里有很多bug,达不到系统使用的标准。
首先存在的问题有:转出账户是否存在是否存在?转出账户的的金额是否够转出?转入账户是否存在?
要解决这些问题需要增加业务校验逻辑,接下来我们改造我们的存储过程。
create or replace procedure proc_trans_value(p_acid_out number,p_acid_in number,p_value number) is
l_cnt number(8):=0;
l_value account.value%type;
begin
select count(1) into l_cnt from account where accountid=p_acid_out;
if l_cnt=1 then
select value into l_value from account where accountid=p_acid_out;
if l_value>=p_value then
update account set value=value-p_value where accountid=p_acid_out;
else
raise_application_error(-20001,'[转出账户金额不足]');
end if;
else
raise_application_error(-20001,'[转出账户不存在]');
end if;

select count(1) into l_cnt from account where accountid=p_acid_in;
if l_cnt=1 then
update account set value=value+p_value where accountid=p_acid_in;
else
raise_application_error(-20001,'[转入账户不存在]');
end if;
end proc_trans_value;

用户自定义异常
raise_application_error(-20001,'[转入账户不存在]');
参数1:错误号,范围:-20000到-20999
参数2:错误消息

作业
1.扩展转账业务,增加一个操作日志表(序号,账户,操作,金额,时间),
当账户表发生改变时增加操作日志表信息。
2.按照部门(scott用户下dept表)经营情况给职员(scott用户下emp表)调薪,
调薪幅度为ACCOUNTING 5%, RESEARCH 7%, SALES 8%, OPERATIONS 6%.


2.函数
用于返回特定的值
语法:
create [or replace] function func_name
(argument1 [mode1] datatype1,
argument2 [mode2] datatype2,
.....) return datatype --函数头部必须要带有RETURN子句,至少要包含一条RETURN语句
is|as
pl/sql block;

2.1 建立函数:不带任何参数
例1:
create or replace function func_user return varchar2
is
v_user varchar2(100);
begin
select username into v_user from user_users;
return v_user;
end;
/

create or replace function func_userid return number
is
v_userid number;
begin
select user_id into v_userid from user_users;
return v_userid;
end;
/
2.2 函数调用
使用变量接受函数返回值
在Command窗口调用
sql>var v1 varchar2(100);
exec :v1:=func_user;
print v1;
在SQL语句中直接调用函数
select func_user from dual;
使用DBMS_OUTPUT调用函数
set serveroutput on;
exec dbms_output.put_line('当前数据库用户:'||func_user);
总结:函数主要是通过SQL进行调用的。

2.3 建立函数:带有IN参数
例2:
create or replace function func_emp_sal(p_ename in varchar2) return number
is
v_result emp.sal%type;
begin
select sal into v_result from emp where ename=upper(p_ename);
return v_result;
end func_emp_sal;
/
show error;
--在函数中select into语句不会报未找到数据的错误。
select func_emp_sal('scott') from dual;
select ename,func_emp_sal(ename) from emp;

练习:用函数实现输入部门号输出部门名。
select func_dname(10) from dual;
select emp.*,func_dname(deptno) dname from emp;

2.4 建立函数:带有out参数
例3:
create or replace function func_dept_dname(p_ename varchar2,
p_job out varchar2) return varchar2
as
v_dname dept.dname%type;
begin
select a.job,b.dname into p_job,v_dname from emp a,dept b
where a.deptno=b.deptno and upper(a.ename)=upper(p_ename);
return v_dname;
end;
/
show err;
不能使用SQL调用
sql>var job varchar2(20);
--select func_dept_dname('scott',:job) from dual;
在command窗口内调用
sql>var job varchar2(20);
var dname varchar2(20);
exec :dname:=func_dept_dname('scott',:job);
在匿名块内调用
declare
v_dname dept.dname%type;
v_job emp.job%type;
begin
v_dname:=func_dept_dname('scott',v_job);
dbms_output.put_line('部门名:'||v_dname||' 职位:'||v_job);
end;

--输出2个值
create or replace function func_dname_job2(
p_ename in varchar2) return varchar2
is
v_rs varchar2(100);
begin
select dname||','||job into v_rs from emp,dept
where emp.deptno=dept.deptno and ename=upper(p_ename);
return v_rs;
end func_dname_job2;
/
show error;

--全部输出
select func_dname_job2('scott') str from dual;
--取分隔符位置
select instr(func_dname_job2('scott'),',') from dual;
--取部门信息
select substr(str,1,instr(str,',')-1)
from (select func_dname_job2('scott') str from dual);
--取职位信息
select substr(str,instr(str,',')+1)
from (select func_dname_job2('scott') str from dual);


练习:拼接同一个部门下的职员名
create or replace function func_emp_ename(p_deptno number) return varchar2
is
v_ename varchar2(100):='';
begin
for rs in (select ename from emp where deptno=p_deptno) loop
v_ename:=v_ename||' '||rs.ename;
end loop;
v_ename:=substr(v_ename,2);
return v_ename;
end;
/

2.5 建立函数:带有IN OUT参数
例4:
create or replace function func_inout(
p_num1 number,p_num2 in out number) return number
is
v_rs number;
begin
v_rs:=trunc(p_num1/p_num2);
p_num2:=mod(p_num1,p_num2);
return v_rs;
end func_inout;
/
show error;
在command窗口内调用
sql>var result1 number;
var result2 number;
exec :result2:=30;
exec :result1:=func_result(100,:result2);
在匿名块内调用
declare
v_shang number;
v_yushu number;
begin
v_yushu:=5;
v_shang:=func_inout(11,v_yushu);
dbms_output.put_line('商:'||v_shang||' 余数:'||v_yushu);
end;

2.6 查看函数源代码
oracle会将函数名及其源代码信息存放到数据字典中user_source
set pagesize 40
select text from user_source where lower(name)='func_result';

2.7.删除函数
drop function func_name;

2.8 编译函数
alter function func_name compile;

2.9 函数调用限制
SQL只能调用带有输入参数,不能带有输出,输入输出函数
SQL不能调用使用返回值是PL/SQL特有数据类型(boolean,table,record等)的函数
SQL语句中调用的函数不能包含INSERT,UPDATE和DELETE语句

作业:
年份转大写:要求2017年输出“二0一七”
月份转大写:要求9月输出“九”,12月输出“十二”
日转大写: 要求9日输出“九”,31日输出“三十一”
金额转大写:要求10234567.89 输出“壹仟零贰拾叁万肆仟伍佰陆拾柒圆零捌角九分”

3. 管理子程序
3.1 列出当前用户的子程序
数据字典视图USER_OBJECTS用于显示当前用户所包含的所有对象.
(表,视图,索引,过程,函数,包)
sql>col object_name format a20
select object_name,created,status from user_objects
where lower(object_type) in ('procedure','function');

3.2 列出子程序源代码
select text from user_source where name='PROC_DEPT_ADD';

3.3 列出子程序编译错误
使用SHOW ERRORS命令确定错误原因和位置
show errors procedure PROC_DEPT_ADD

使用数据字典视图USER_ERRORS确定错误原因和位置
col text format a50
select line||'/'||position as "line/col",text error from user_errors
where name='PROC_DEPT_ADD';

3.4 列出对象依赖关系
使用数据字典视图USER_DEPENDENCIES确定直接依赖关系
select name,type from user_dependencies where referenced_name='EMP';

使用工具视图DEPTREE和IDEPTREE确定直接依赖和间接依赖关系
先运行SQL脚本UTLDTREE.SQL来建立这两个视图和过程DEPTREE_FILL,然后调用DEPTREE_FILL填充这两个视图
sql>@%oracle_home% dbmsadminutldtree
exec deptree_fill('TABLE','scott','emp')
执行后会将直接或间接依赖于SCOTT.EMP表的所有对象填充到视图DEPTREE和IDEPTREE中.
select nested_level,name,type from deptree;
select * from ideptree

3.5 重新编译子程序
当修改了被引用对象的结构时,就会将相关依赖对象转变为无效(INVALID)状态。
alter table emp add remark varchar2(10);
select object_name,object_type,status from user_objects where lower(status)='invalid';
为了避免子程序的运行错误,应该重新编译这些存储对象
alter procedure add_employee compile;
alter view dept10 compile;
alter function FUNC_EMP_SAL compile;