一般来说过程和函数被称为子程序。过程是一段不具有返回值的代码块,而函数会返回一个值。子程序与匿名块的最大不同是它们可以存储到数据库的字典中,以便重用。
向dept表插入记录的过程
create or replace procedure newdept(
p_deptno dept.deptno%TYPE,
p_dname dept.dname%TYPE,
p_loc dept.loc%TYPE
)
AS
v_deptcount NUMBER;
begin
select count(*) into v_deptcount from dept where deptno=p_deptno;
if v_deptcount>0
then
raise_application_error(-20002,'出现了相同的员工记录');
end if;
insert into dept(deptno,dname,loc) values(p_deptno,p_dname,p_loc);
commit;
end newdept;
调用上面这个过程
begin
newdept(10,'成本科','深圳');
exception
when others then
dbms_output.put_line('产生了错误:'||SQLERRM);
end;
子程序的优点
提供模块化功能:模块化是将一个大的代码块打散为多个小的易于管理的子程序,由其他模块调用,使得代码具有更强的可读性和可维护性。
更强的可管理型:大多数程序员都不愿意看到一个超过1000行的语句块这种写法,因为管理起来相当困难,特别是在后期维护时,如果使用子程序,则可以将这1000行代码打散,提供各个子程序进行调用,既方便调试,又提供了较强的可管理性
增强的可读性:每个子程序都具有描述性的命名,使得程序的阅读者能够很容易地了解子程序的功能,进而容易理解和把握整个程序,子程序使得阅读代码的人首先看到一个人的实现结构,而不用一开始就关注到具体的可执行语句的细节
更强的稳定性:子程序便于调试,使得代码具有较少的错误。
create or replace procedure newdept(
p_deptno IN number,
p_dname IN varchar2,
p_loc IN varchar2
)
AS
v_deptcount number(4);
e_duplication_dept exception;
begin
select count(*) into v_deptcount from dept
where deptno=p_deptno;
if v_deptcount>0
then
raise e_duplication_dept;
end if;
insert into dept(deptno,dname,loc) values (p_deptno,p_dname,p_loc);
commit;
exception
when e_duplication_dept then
rollback;
raise_application_error(-20002,'出现了相同的员工记录');
end newdept;
可以看到,在为过程传递参数时,参数名称统一以p开头,这只是一种约定,并不是原则,IN关键字用来指明参数为输入参数,参数的类型并没有指定长度:AS之后的语句是局部定义区,在示例中定义了一个变量和异常,这类似于匿名块的Declare区域;begin之后是过程的代码执行区,当员工编号重复时,将触发异常;exception捕获了这个异常,使用RAISE_APPLICATION_ERROR向调用方传递友好的错误消息。
函数体内部,包含了多个return语句用来将函数的值返回给调用环境。
一个函数体内可以有多个return语句,但只有一个return语句会被执行,当执行到return语句时,函数将不再往下执行,控制会立即返回到调用函数的环境中。
create or replace function getraisedsalary(p_empno emp.empno%TYPE)
return number
is
v_job emp.job%TYPE;
v_sal emp.sal%TYPE;
v_salaryratio number(10,2);
begin
select job,sal into v_job,v_sal from emp where empno=p_empno;
case v_job
when '职员' then
v_salaryratio:=1.09;
when '销售人员' then
v_salaryratio:=1.11;
when '经理' then
v_salaryratio:=1.18;
else
v_salaryratio:=1;
end case;
if v_salaryratio<>1
then
return round(v_sal * v_salaryratio,2);
else
return v_sal;
end if;
exception
when no_data_found then
return 0;
end getraisedsalary;
调用函数
declare
v_raisedsal number(10,2);
begin
dbms_output.put_line('7369员工调薪记录:'||getraisedsalary(7369));
v_raisedsal:=getraisedsalary(7521);
dbms_output.put_line('7521员工调薪记录:'||getraisedsalary(7521));
end;
在过程中return语句不返回值,也不返回任何表达式,它的作用是立即退出过程的执行,将控制权返回给过程的调用者。
在函数中return语句必须包含一个表达式,表达式的值会在return语句执行时被计算,然后赋给在声明中的return语句中指定的数据类型的变量,也就是函数标识符,再将控制权返回给调用者。
create or replace procedure raisesalary(
p_empno emp.empno%TYPE
)
AS
v_job emp.job%TYPE;
v_sal emp.sal%TYPE;
begin
select job,sal into v_job,v_sal from emp where empno=p_empno;
if v_job<>'职员' then
return;
elsif v_sal>3000 then
return;
else
update emp set sal=round(sal*1.12,2) where empno=p_empno;
end if;
exception
when no_data_found then
dbms_output.put_line('没有找到员工记录');
end raisesalary;
在产生了错误后,可以通过user_errors将所有的错误都显示出来。
select line,position,text from user_errors where name='RAISESALARY' order by sequence;
形参与实参
形式参数:在定义子程序时,在定义语句中定义的参数称为形式参数。简称形参
实际参数:在调用子程序时,传入的具体参数值称为实际参数,简称实参。
子程序在定义过程中定义的参数及在过程体中使用的参数都是形式参数,这些参数不具有实际的值,仅是过程需要的数据的占位符。
create or replace procedure insertdept(
p_deptno number, --形参
p_dname varchar2,
p_loc varchar2
)
AS
v_count number(10);
begin
select count(deptno) into v_count from dept where deptno=p_deptno;
if v_count>1 then
raise_application_error(-20001,'数据库中存在相同名称的部门编号');
end if;
insert into dept values(p_deptno,p_dname,p_loc);
commit;
end insertdept;
调用时
insertdept(55,'行政部','德克萨斯'); 实参
参数模式
形式参数的模式用来控制形式参数的行为,一般有3中类型的模式:IN OUT 和IN OUT 如果没有指定参数的模式 默认为IN模式
1.IN模式
IN模式的参数称为输入参数,这是默认的参数模式。IN模式直接把值传递给调用子程序,IN模式的参数就像常量一样,不能被赋值,可以为IN模式的参数初始化一个默认值。
create or replace procedure insertdept(
p_deptno IN number:=55, --形参 并赋初值
p_dname IN varchar2,
p_loc IN varchar2
)
AS
v_count number(10);
begin
--p_dname:='市场策略部'; --错误 不能对IN模式参数进行赋值
select count(deptno) into v_count from dept where deptno=p_deptno;
if v_count>1 then
raise_application_error(-20001,'数据库中存在相同名称的部门编号');
end if;
insert into dept values(p_deptno,p_dname,p_loc); --在过程体中使用形式参数
commit;
end insertdept;
调用
begin
insertdept(55,'勤运部','西北');
end;
2.OUT模式
OUT模式的参数又称为输出参数,输出参数将会改变参数的值,因此实际参数不能用文字或常量来表示,必须要先定义一个变量,然后将该变量作为一个实际参数使用来调用过程。
create or replace procedure outraisesalary(
p_empno IN number, --定义一个员工加薪后的薪资的输出变量
p_raisedsalary OUT number
)
AS
v_sal number(10,2); --定义本地局部变量
v_job varchar2(10);
begin
p_raisedsalary:=0;
select sal,job into v_sal,v_job from emp where empno=p_empno; --查询员工信息
if v_job='职员' then
p_raisedsalary:=v_sal * 1.12; --对OUT模式的参数进行赋值是合法的
update emp set sal=p_raisesalary where empno=p_empno;
else
p_raisesalary:=v_sal; --否则赋原来的薪资值
end if;
exception
when no_data_found then --异常处理语句块
dbms_output.put_line('没有找到该员工的记录');
end outraisesalary;
调用
declare
v_raisedsalary number(10,2); --定义一个变量保存输出值
begin
v_raisedsalary:=100; --这个赋值在传入到outraisesalary后被忽略
outraisesalary(7369,v_raisedsalary); --调用函数
dbms_output.put_line(v_raisedsalary); --显示输出参数的值
end;
v_raisedsalary这个变量用来传给outraisesalary过程中的输出参数作为实际参数,尽管在过程体中为该变量赋了初值,但是当调用outraisesalary时,该变量的任何初始值都会被忽略。
OUT模式的形式参数会被初始化为NULL,所以形参的数据类型是不能有NOT NULL约束的,比如内置类型NATURALN和POSITIVEN,否则PL/SQL会抛出VALUE_ERROR异常
3.IN OUT模式
IN OUT模式是IN和OUT方式的组合,又称为输入/输出参数,当过程被调用时,实际参数的值被传递给过程,形式参数可以被读出和写入,当过程结束,控制返回到调用环境时,形式参数的内容被赋给实际参数。在过程内部,输入/输出参数就像是一个初始化了的变量,可以从变量中读取值,也可以为其赋值而产生输出。IN OUT 模式示例:
create or replace procedure calcraisedsalary(
p_job IN varchar2,
p_salary IN OUT number --定义输出/输入参数
)
AS
v_sal number(10,2); --保存调整后的薪资值
begin
if p_job='职员' then --根据不同的job进行薪资的调整
v_sal:=p_salary * 1.12;
elsif p_job='销售人员' then
v_sal:=p_salary * 1.18;
elsif p_job='经理' then
v_sal:=p_salary * 1.19
else
v_sal:=p_salary;
end if;
p_salary:=v_sal; --将调整后的结果赋给输入/输出参数
end calcraisedsalary;
调用
declare
v_sal number(10,2); --薪资变量
v_job varchar2(10); --职位变更
begin
select sal,job into v_sal,v_job from emp where empno=7369; --获取薪资和职位信息
calcraisedsalary(v_job,v_sal); --计算调薪
dbms_output.put_line('计算后的调整薪水为:'|| v_sal); --获取调薪后的结果
end;
形式参数的约束
在过程被调用时,将传入实际参数的值,在过程定义时,形式参数不能指定长度的约束,任何指定长度或精度来约束都是不合法的。
形式参数不能用约束声明,但是可以使用%TYPE对其进行约束。
create or replace procedure calcraisedsalarywithtype(
p_job IN emp.job%TYPE
)
要是改成p_job IN varchar2(10) 就是错的 不能加长度 或精度 约束
只有在
declare
v_sal number(10,2);
begin
calcraisedsalarywithtype(v_sal);
end;
参数传递方式
在调用子程序时,有两种向子程序的形式参数传递实际参数的方式:一种是前面见过的按位置传递;另一种是按名称传递。
大多数参数传递的场合都使用按位置传递方式,只要传入的实际参数的位置匹配形式参数的位置定义即可。例如过程接收2个参数
calcraisesalary(v_job,v_sal);
还可以使用类似的按名称传递方法,这种方法将使用=>作为关联的操作符,把左边的实参和右边的形参关联起来,因此上述调用也可以写为如下形式:
calcraisesalary(p_job=>v_job,p_salary=>v_sal);
可以看到,p_job与p_salary是在过程定义中使用的形式参数,而v_sal与v_job是实际参数,由于使用了名字表示法,因此参数的位置就显得不那么重要了,因此如下的调用也是合法的:
calcraisesalary(p_job=>v_job,p_salary=>v_sal);
还可以在过程调用中混合使用按位置传递与按名称传递两种方式,但是在这种情况下,位置标示法必须在名字标示法之前,不能反过来使用。
calcraisesalary(v_job,p_salary=>v_sal);
如果反过来,将名称调用法放在前面,则是非法的
calcraisesalary(p_salary=>v_sal,v_job);
参数默认值
在定义子程序时,可以使用default关键字或赋值语句为IN模式的参数指定默认值,在调用子程序时可以根据需要传递不同个数的参数。
create or replace procedure newdeptwithdefault(
p_deptno dept.deptno%TYPE default 57,
p_dname dept.dname%TYPE:='管理部',
p_loc dept.loc%TYPE default '江苏'
)
AS
v_deptcount number;
begin
select count(*) into v_deptcount from dept where deptno=p_deptno;
if v_deptcount>0
then
raise_application_error(-20002,'出现了相同的员工记录');
end if;
insert into dept(deptno,dname,loc) values(p_deptno,p_dname,p_loc);
end;
调用方法
begin
newdeptwithdefault; --不指定任何参数,将使用形参默认值
end;
begin
newdeptwithdefault(58,'事务组'); ---让loc参数使用默认值
end;
begin
newdeptwithdefault(p_deptno=>58,p_loc=>'南海'); --让dname使用默认值
end;
使用NOCOPY编译提示
值传递:当参数通过值传递时,参数将从实际参数中被复制形式参数中。
引用传递:实际参数的指针被传递到了相应的形式参数中。
当要在过程定义中存在NOCOPY时,PLSQL编译器将通过引用传递参数,但是由于NOCOPY只是一个编译器提示,而不是一个指令,因此并不一定会使用引用传递,但是多数情况下是可行的。
create or replace procedure nocopydemo
(
p_inparameter in number,
p_inoutparameter in out nocopy varchar2, --使用NOCOPY编译提示
p_outparameter out nocopy varchar2
)
IS
begin
NULL;
end;
NOCOPY通常用在out和in out这类按值传递的形式参数中,并且是参数占用大量内存的场合,以避免复制数据带来的性能开销。
declare
TYPE emptabtyp IS table of emp%ROWTYPE; --定义嵌套表类型
emp_tab emptabtyp:=emptabtyp(NULL); --定义一个空白的嵌套表变量
t1 number(5);
t2 number(5);
t3 number(5);
procedure get_time(t out number) --获取当前时间
IS
begin
select to_char(SYSDATE,'SSSSS') into t from dual;
end;
procedure do_nothing1(tab in out emptabtyp)
IS
begin
NULL;
end;
procedure do_nothing2(tab in out NOCOPY emptabtyp)
IS
begin
NULL;
end;
begin
select * into emp_tab(1) from emp where empno=7788;
emp_tab.EXTEND(900000,1);
get_time(t1);
do_nothing1(emp_tab);
get_time(t2);
do_nothing2(emp_tab);
get_time(t3);
dbms_output.put_line('调用所花费的时间秒');
dbms_output.put_line('------------------');
dbms_output.put_line('不带NOCOPY的调用:'||TO_CHAR(t2-t1));
dbms_output.put_line('带NOCOPY的调用:'||TO_CHAR(t3-t2));
end;
在SQL中调用子程序
1.所有函数的参数必须是IN模式,IN OUT 和OUT模式的参数是不能被SQL语句使用的
2.函数参数的数据类型和RETURN子句的返回类型,必须能被oracle数据库识别,这是因为PL/SQL兼容所有的oracle数据类型,但是PL/SQL扩充了自己的类型,比如BOOLEAN,INTEGER,记录,集合和程序员定义的子类型等
3.函数必须被存储在数据库中,在客户端PL/SQL环境中定义的PL/SQL函数是不能被SQL语句调用得到的
除了这些规则之外,在SQL中调用PL/SQL自定义的函数还具有如下的约束。
函数不能修改数据库表,它不能执行任何下面的语句:DDL语句,比如CREATE TABLE,DROP INDEX等,不能在函数中使用COMMIT或ROLLBACK提交或回滚事务,当函数定义在自治事务中时,这个限制稍稍宽松一些。因为在自治事务中会与调用事务独立出来。
自治事务
1.当调用远程的函数或通过并行行为调用其他会话中的函数时,函数可能不能读取或写入包变量的值,因为oracle服务器不支持跨用户会话访问。
2.仅当函数在一个select列表中被调用时,或者是values或set子句中,函数才能够更新包变量的值,如果在where或group by子句中,它可能无法改写包变量的值
3.在oracle8i之前的版本中,不能在用户自定义函数中调用RAISE_APPLICATION_ERROR抛出异常。
4.函数不能调用其他模块(比如说存储过程或函数),否则将打断任何先前定义的规则
5.函数不能引用一个视图,视图也就是存储了select语句的查询
create or replace function getempdept(
p_empno emp.empno%TYPE
)RETURN VARCHAR2
AS
v_dname dept.dname%TYPE;
begin
select b.dname into v_dname from emp a,dept b where a.deptno=b.deptno and a.empno=p_empno;
return v_dname;
exception
when no_data_found then
return null;
end;
嵌套子程序
内嵌子程序是一个过程或函数,它定义在PL/SQL块的声明区中(可以是命名块或匿名块),定义在块的声明区中的子程序仅能被这个块调用,不能被任何定义在块外部的子程序或块调用,嵌套子程序与其外部块的结构,嵌套子程序的作用域只能位于其定义的块本身,不能被外部块调用。在语句块中使用嵌套子程序有如下几个好处
1.通过提取重复代码到嵌套子程序中,可以减小外部块的代码大小。
2.可以提升代码的可读性
create or replace function getraisedsalary_subprogram(p_empno emp.empno%TYPE)
return number
is
v_salaryratio number(10,2);
v_sal emp.sal%TYPE;
function getratio(p_sal out number) return number is
n_job emp.job%TYPE;
n_salaryratio number(10,2);
begin
select job,sal into n_job,p_sal from emp where empno=p_empno;
case n_job
when '职员' then
n_salaryratio:=1.09;
when '销售人员' then
n_salaryratio:=1.11;
when '经理' then
n_salaryratio:=1.18;
else
n_salaryratio:=1;
end case;
return n_salaryratio;
end;
begin
v_salaryratio:=getratio(v_sal);
if v_salaryratio <> 1
then
return round(v_sal * v_salaryratio,2);
else
return v_sal;
end if;
exception
when no_data_found then
return 0;
end;
嵌套子程序仅能定义在变量声明语句的里面,否则会报错,子程序不需要单独存储在数据字典中
调用
begin
dbms_output.put_line('7369员工调薪记录:'||getraisedsalary_subprogram(7369));
dbms_output.put_line('7521员工调薪记录:'||getraisedsalary_subprogram(7521));
end;
嵌套子程序与存储子程序的异同
存储子程序
1.子程序以编译的形式存储在数据字典中,当调用过程时,不需要重新编译
2.子程序可以从对该子程序具有EXECUTE权限的用户所提交的任何语句块中调用
3.通过将子程序代码与调用块分开,使调用块更短,更容易理解和维护,如果愿意,子程序和调用块还可以分开来进行维护
4.可以使用dbms_shared_pool.keep包过程把已编译伪代码锁定在共享池中以便重用,这样可以改进其性能。
5.独立的存储子程序不能重载,但是包中的子程序可以在同一个包中重载
嵌套子程序
1.嵌套子程序被编译为包含它的语句块的一部分,如果包含它的语句块是匿名的,并多次运行,那么每次都必须编译子程序
2.嵌套子程序只能从包含子程序的语句块中调用
3.子程序和调用块是完全相同的,这会导致混乱,如果对调用块进行了更改,那么子程序也将被编译为包含它的块的重编译块的一部分
4.嵌套子程序不能被其他子程序锁定在共享池中
5.嵌套子程序可以在同一个语句块中重载
子程序的前向声明
PL/SQL支持递归调用,因此两个或多个子程序可以互相调用对象,嵌套子程序的互调。
declare
v_val binary_integer:=5;
procedure a(p_counter in out binary_integer) is
begin
dbms_output.put_line('A('||p_counter||')');
if p_counter>0 then
b(p_counter);
p_counter:=p_counter-1;
end if;
end a;
procedure b(p_counter in out binary_integer) is
begin
dbms_output.put_line('b('||p_counter||')');
p_counter:=p_counter-1;
a(p_counter);
end b;
begin
b(v_val);
end;
上面代码会产生异常, 在此作用域没有声明b的异常,此时可以使用前向声明(或称预声明),前向声明仅包含子程序的结构定义,并不包含具体的实现代码,这种声明也用于包中的包头中。
declare
v_val binary_integer:=5;
procedure b(p_counter in out binary_integer);
procedure a(p_counter in out binary_integer) is
begin
dbms_output.put_line('a(’||p_counter||')');
if p_counter>0 then
b(p_counter);
p_counter:=p_counter-1;
end if;
end a;
procedure b(p_counter in out binary_integer) is
begin
dbms_output.put_line('b('||p_counter||')');
p_counter:=p_counter-1;
a(p_counter);
end b;
begin
b(v_val);
end;
重载子程序
重载是面向对象的编程语言中非常常见的一种编写对象方法的方式,重载是指具有相同名称的方法,但是在参数的类型或顺序或是数据类型上有所不同。在PLSQL的包中可以编写重载特性的子程序。
在PLSQL块中嵌套子程序重载的原则与包或对象的重载相似,重载的子程序具有相同的名称,但是参数的类型,顺序或数据类型不同。
declare
procedure getsalary(p_empno IN number) is --带一个参数的过程
begin
dbms_output.put_line('员工编号为:'||p_empno);
end;
procedure getsalary(p_empname IN varchar2) is --重载的过程
begin
dbms_output.put_line('员工名称为:'||p_empname);
end;
procedure getsalary(p_empno in number,p_empname in varchar) is
begin
dbms_output.put_line('员工编号为:'||p_empno||'员工名称为:'||p_empname);
end;
begin
getsalary(7369); --调用重载方法
getsalary('史密斯');
getsalary(7369,'史密斯');
end;
子程序自治事务
当进行子程序开发时,如果想独立于主事务开始一个独立的事务,在子程序中使用COMMIT或ROLLBACK语句时,不影响主事务的状态,这种事务称为自治事务
自治事务是由主事务开启的独立事务,自治事务把主事务挂起来,然后执行SQL操作,在提交或回滚这些操作后,重新恢复主事务。自治事务与主事务的关系。
自治事务使用AUTONOMOUS_TRANSACTION编译提示来定义,这个编译提示会让PLSQL编译器把子程序或数据库触发器标记为自治的事务。可以将这个指令放到程序声明部分的任何地方,但是为了良好的可读性,一般把它放到声明区的顶部,基本语法如下:
PRAGMA AUTONOMOUS_TRANSACTION;
declare
procedure testautonomous(p_empno number) as
PRAGMA AUTONOMOUS_TRANSACTION; --标记为自治事务
begin
insert into emp_history select * from emp where empno=p_empno;
commit;
end testautonomous;
begin
insert into emp_history(empno,ename,sal) values(1011,'测试',1000);
testautonomous(7369);
rollback
end;
递归调用子程序
递归是一个非常重要而且在日常工作中使用非常频繁的算法,它通过不断地调用自身来实现层次化的处理工作。
递归算法中具有如下两个规则。
1.必须要有一个方法能够退出递归循环,以免永远地递归下去。如果递归程序无限制地执行下去,PL/SQL最终会用光内存然互抛出预定义的STORAGE_ERROR异常.
2.要有一个方法调用自身,通过不断地改变条件来使得递归接近退出的位置。
递归阶乘
declare
v_result integer;
function fac(n positive)
return integer is
begin
if n=1 then
dbms_output.put('1!=1*0!');
return 1;
else
dbms_output.put(n||'!='||n||'*');
return n*fac(n-1);
end if;
end fac;
begin
v_result:=fac(10);
dbms_output.put_line('结果是:'||v_result);
end;
使用递归查找职员列表示例
declare
procedure find_staff(mgr_no number,tier number:=1)
is
boss_name varchar2(10);
cursor c1 (boss_no number)
is
select empno,ename from emp where mgr=boss_no;
begin
select ename into boss_name from emp
where empno=mgr_no;
if tier=1
then
insert into staff values(boss_name||'是老板');
end if;
for ee in c1 (mgr_no)
loop
insert into staff values (boss_name
||'管理'
||ee.ename
||'在层次'
||TO_CHAR(tier));
find_staff(ee.empno,tier+1);
end loop;
commit;
end find_staff;
begin
find_staff(7839);
end;
理解子程序依赖性
在创建子程序时,往往需要引用到其他的对象,比如过程insertdept需要更新dept表,那么可以将过程insertdept称为对dept表的对象依赖,而将表dept称为被引用对象。其中对象依赖又包含了两种情况:直接依赖和间接依赖。
子程序依赖示例
create or replace procedure testdependence as
begin
insert into emp(empno,ename,sal) values(1011,'测试',1000);
testsubprog(7369);
rollback;
end;
被另一个程序调用,用来向表emp_history表插入数据
create or replace procedure testsubprog(p_empno number)as
begin
insert into emp_history select * from emp where empno=p_empno;
end testsubprog;
查看依赖
对于直接依赖
select name,type from user_dependencies where referenced_name='EMP';
查看间接依赖性
1.首先执行一下 ADMIN\utldtree.sql
2.EXEC deptree_fill('TABLE','SCOTT','EMP');
3.select nested_level,name,type from deptree where type in('PROCEDURE','FUNCTION');
nested_level是依赖的层次数据,如果是1则表示为直接依赖,如果是2则表示为间接依赖。
查看对象有效性
当对字程序直接或间接依赖的对象进行修改之后,比如DDL 那么存储子程序就可能变得无效
1.alter table emp_history add emp_desc varchar2(200) NULL;
2.select object_name,object_type,status from user_objects where object_name in('TESTDEPENDENCE','TESTSUBPROG');
重新编译
如果一个依赖对象失效,试图下一次调用时重新编译,但并不是都有效的。
1.alter table emp_history drop column emp_desc;
2.alter procedure testdependence compile;
3.alter procedure testsubprog compile;
4.select object_name,boject_type,status from user_objects where object_name in ('TESTDEPENDENCE','TESTSUBPROG');
子程序权限管理
其他用户调用scott的
grant execute on find_staff to userb;
begin
scott.find_staff(7839);
end;
也可以使用同义词