`
yxw22
  • 浏览: 26513 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Oracle存储过程学习

阅读更多

存储过程创建语法:

       create or replace procedure 存储过程名(param1 in type,param2 out type)

as

变量1 类型(值范围);

变量2 类型(值范围);

Begin

    Select count(*) into 变量1 from 表A where列名=param1;

    If (判断条件) then

       Select 列名 into 变量2 from 表A where列名=param1;

       Dbms_output。Put_line(‘打印信息’);

    Elsif (判断条件) then

       Dbms_output。Put_line(‘打印信息’);

    Else

       Raise 异常名(NO_DATA_FOUND);

    End if;

Exception

    When others then

       Rollback;

End;

 

注意事项:

1,  存储过程参数不带取值范围,in表示传入,out表示输出

2,  变量带取值范围,后面接分号

3,  在判断语句前最好先用count(*)函数判断是否存在该条操作记录

4,  用select 。。。into。。。给变量赋值

5,  在代码中抛异常用 raise+异常名

 

 

以命名的异常

命名的系统异常                          产生原因

ACCESS_INTO_NULL                   未定义对象

CASE_NOT_FOUND                     CASE 中若未包含相应的 WHEN ,并且没有设置

ELSE 时

COLLECTION_IS_NULL                集合元素未初始化

CURSER_ALREADY_OPEN          游标已经打开

DUP_VAL_ON_INDEX                   唯一索引对应的列上有重复的值

INVALID_CURSOR                 在不合法的游标上进行操作

INVALID_NUMBER                       内嵌的 SQL 语句不能将字符转换为数字

NO_DATA_FOUND                        使用 select into 未返回行,或应用索引表未初始化的 

 

TOO_MANY_ROWS                      执行 select into 时,结果集超过一行

ZERO_DIVIDE                              除数为 0

SUBSCRIPT_BEYOND_COUNT     元素下标超过嵌套表或 VARRAY 的最大值

SUBSCRIPT_OUTSIDE_LIMIT       使用嵌套表或 VARRAY 时,将下标指定为负数

VALUE_ERROR                             赋值时,变量长度不足以容纳实际数据

LOGIN_DENIED                           PL/SQL 应用程序连接到 oracle 数据库时,提供了不

正确的用户名或密码

NOT_LOGGED_ON                       PL/SQL 应用程序在没有连接 oralce 数据库的情况下

访问数据

PROGRAM_ERROR                       PL/SQL 内部问题,可能需要重装数据字典& pl./SQL

系统包

ROWTYPE_MISMATCH                宿主游标变量与 PL/SQL 游标变量的返回类型不兼容

SELF_IS_NULL                             使用对象类型时,在 null 对象上调用对象方法

STORAGE_ERROR                        运行 PL/SQL 时,超出内存空间

SYS_INVALID_ID                         无效的 ROWID 字符串

TIMEOUT_ON_RESOURCE         Oracle 在等待资源时超时 
---------------------------------------------------------------------------------------------------------------------

oracle 存储过程的基本语法


1.基本结构

CREATE OR REPLACE PROCEDURE 存储过程名字
(
    参数1 IN NUMBER,
    参数2 IN NUMBER
) IS
变量1 INTEGER :=0;
变量2 DATE;
BEGIN

END 存储过程名字

2.SELECT INTO STATEMENT
  将select查询的结果存入到变量中,可以同时将多个列存储多个变量中,必须有一条
  记录,否则抛出异常(如果没有记录抛出NO_DATA_FOUND)
  例子:
  BEGIN
  SELECT col1,col2 into 变量1,变量2 FROM typestruct where xxx;
  EXCEPTION
  WHEN NO_DATA_FOUND THEN
      xxxx;
  END;
  ...

3.IF 判断
  IF V_TEST=1 THEN
    BEGIN
       do something
    END;
  END IF;

4.while 循环
  WHILE V_TEST=1 LOOP
  BEGIN
 XXXX
  END;
  END LOOP;

5.变量赋值
  V_TEST := 123;

6.用for in 使用cursor

  ...
  IS
  CURSOR cur IS SELECT * FROM xxx;
  BEGIN
 FOR cur_result in cur LOOP
  BEGIN
   V_SUM :=cur_result.列名1+cur_result.列名2
  END;
 END LOOP;
  END;

7.带参数的cursor
  CURSOR C_USER(C_ID NUMBER) IS SELECT NAME FROM USER WHERE TYPEID=C_ID;
  OPEN C_USER(变量值);
  LOOP
 FETCH C_USER INTO V_NAME;
 EXIT FETCH C_USER%NOTFOUND;
    do something
  END LOOP;
  CLOSE C_USER;

8.用pl/sql developer debug
  连接数据库后建立一个Test WINDOW
  在窗口输入调用SP的代码,F9开始debug,CTRL+N单步调试

关于oracle存储过程的若干问题备忘

1.在oracle中,数据表别名不能加as,如:

select a.appname from appinfo a;-- 正确
select a.appname from appinfo as a;-- 错误
 也许,是怕和oracle中的存储过程中的关键字as冲突的问题吧

2.在存储过程中,select某一字段时,后面必须紧跟into,如果select整个记录,利用游标的话就另当别论了。

  select af.keynode into kn from APPFOUNDATION af where af.appid=aid and af.foundationid=fid;-- 有into,正确编译
  select af.keynode from APPFOUNDATION af where af.appid=aid and af.foundationid=fid;-- 没有into,编译报错,提示:Compilation 
  Error: PLS-00428: an INTO clause is expected in this SELECT statement

3.在利用select...into...语法时,必须先确保数据库中有该条记录,否则会报出"no data found"异常。

   可以在该语法之前,先利用select count(*) from 查看数据库中是否存在该记录,如果存在,再利用select...into...

4.在存储过程中,别名不能和字段名称相同,否则虽然编译可以通过,但在运行阶段会报错

 select keynode into kn from APPFOUNDATION where appid=aid and foundationid=fid;-- 正确运行
select af.keynode into kn from APPFOUNDATION af where af.appid=appid and af.foundationid=foundationid;-- 运行阶段报错,提示
ORA-01422:exact fetch returns more than requested number of rows

5.在存储过程中,关于出现null的问题

假设有一个表A,定义如下:
create table A(
id 
varchar2(50primary key not null,
vcount 
number(8not null,
bid 
varchar2(50not null -- 外键 
);
如果在存储过程中,使用如下语句:
select sum(vcount) into fcount from A where bid='xxxxxx';
如果A表中不存在bid="xxxxxx"的记录,则fcount=null(即使fcount定义时设置了默认值,如:fcount number(8):=0依然无效,fcount还是会变成null),这样以后使用fcount时就可能有问题,所以在这里最好先判断一下:
if fcount is null then
    fcount:
=0;
end 
if;
这样就一切ok了。

6.Hibernate调用oracle存储过程

        this.pnumberManager.getHibernateTemplate().execute(
                
new HibernateCallback() ...{
                    
public Object doInHibernate(Session session)
                            
throws HibernateException, SQLException ...{
                        CallableStatement cs 
= session
                                .connection()
                                .prepareCall(
"{call modifyapppnumber_remain(?)}");
                        cs.setString(
1, foundationid);
                        cs.execute();
                        
return null;
                    }

                }
);
 
存储过程和函数也是一种PL/SQL块,是存入数据库的PL/SQL块。但存储过程和函数不同于已经介绍过的PL/SQL程序,我们通常把PL/SQL程序称为无名块,而存储过程和函数是以命名的方式存储于数据库中的。和PL/SQL程序相比,存储过程有很多优点,具体归纳如下:
* 存储过程和函数以命名的数据库对象形式存储于数据库当中。存储在数据库中的优点是很明显的,因为代码不保存在本地,用户可以在任何客户机上登录到数据库,并调用或修改代码。
* 存储过程和函数可由数据库提供安全保证,要想使用存储过程和函数,需要有存储过程和函数的所有者的授权,只有被授权的用户或创建者本身才能执行存储过程或调用函数。
* 存储过程和函数的信息是写入数据字典的,所以存储过程可以看作是一个公用模块,用户编写的PL/SQL程序或其他存储过程都可以调用它(但存储过程和函数不能调用PL/SQL程序)。一个重复使用的功能,可以设计成为存储过程,比如:显示一张工资统计表,可以设计成为存储过程;一个经常调用的计算,可以设计成为存储函数;根据雇员编号返回雇员的姓名,可以设计成存储函数。
* 像其他高级语言的过程和函数一样,可以传递参数给存储过程或函数,参数的传递也有多种方式。存储过程可以有返回值,也可以没有返回值,存储过程的返回值必须通过参数带回;函数有一定的数据类型,像其他的标准函数一样,我们可以通过对函数名的调用返回函数值。
   存储过程和函数需要进行编译,以排除语法错误,只有编译通过才能调用。
创建和删除存储过程
创建存储过程,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系统权限。该权限可由系统管理员授予。创建一个存储过程的基本语句如下:
CREATE [OR REPLACE] PROCEDURE 存储过程名[(参数[IN|OUT|IN OUT] 数据类型...)]
{AS|IS}
[说明部分]
BEGIN
可执行部分
[EXCEPTION
错误处理部分]
END [过程名];
其中:
可选关键字OR REPLACE 表示如果存储过程已经存在,则用新的存储过程覆盖,通常用于存储过程的重建。
参数部分用于定义多个参数(如果没有参数,就可以省略)。参数有三种形式:IN、OUT和IN OUT。如果没有指明参数的形式,则默认为IN。
关键字AS也可以写成IS,后跟过程的说明部分,可以在此定义过程的局部变量。
编写存储过程可以使用任何文本编辑器或直接在SQL*Plus环境下进行,编写好的存储过程必须要在SQL*Plus环境下进行编译,生成编译代码,原代码和编译代码在编译过程中都会被存入数据库。编译成功的存储过程就可以在Oracle环境下进行调用了。
一个存储过程在不需要时可以删除。删除存储过程的人是过程的创建者或者拥有DROP ANY PROCEDURE系统权限的人。删除存储过程的语法如下:
DROP PROCEDURE 存储过程名;
如果要重新编译一个存储过程,则只能是过程的创建者或者拥有ALTER ANY PROCEDURE系统权限的人。语法如下:
ALTER PROCEDURE 存储过程名 COMPILE;
执行(或调用)存储过程的人是过程的创建者或是拥有EXECUTE ANY PROCEDURE系统权限的人或是被拥有者授予EXECUTE权限的人。执行的方法如下:
方法1:
EXECUTE 模式名.存储过程名[(参数...)];
方法2:
BEGIN
模式名.存储过程名[(参数...)];
END;
传递的参数必须与定义的参数类型、个数和顺序一致(如果参数定义了默认值,则调用时可以省略参数)。参数可以是变量、常量或表达式,用法参见下一节。
如果是调用本账户下的存储过程,则模式名可以省略。要调用其他账户编写的存储过程,则模式名必须要添加。
以下是一个生成和调用简单存储过程的训练。注意要事先授予创建存储过程的权限。
【训练1】  创建一个显示雇员总人数的存储过程。
步骤1:登录SCOTT账户(或学生个人账户)。
步骤2:在SQL*Plus输入区中,输入以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT   
  2. AS  
  3. V_TOTAL NUMBER(10);   
  4. BEGIN  
  5.  SELECT COUNT(*) INTO V_TOTAL FROM EMP;   
  6.  DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_TOTAL);   
  7. END;  
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT  
  2. AS  
  3. V_TOTAL NUMBER(10);  
  4. BEGIN  
  5.  SELECT COUNT(*) INTO V_TOTAL FROM EMP;  
  6.  DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_TOTAL);  
  7. END;  

步骤3:按“执行”按钮进行编译。
如果存在错误,就会显示:
警告: 创建的过程带有编译错误。
如果存在错误,对脚本进行修改,直到没有错误产生。
如果编译结果正确,将显示:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤4:调用存储过程,在输入区中输入以下语句并执行:
Sql代码 复制代码
  1. EXECUTE EMP_COUNT;  
  1. EXECUTE EMP_COUNT;  

显示结果为:
Sql代码 复制代码
  1. 雇员总人数为:14   
  2.         PL/SQL 过程已成功完成。  
  1. 雇员总人数为:14  
  2.         PL/SQL 过程已成功完成。  

说明:在该训练中,V_TOTAL变量是存储过程定义的局部变量,用于接收查询到的雇员总人数。
注意:在SQL*Plus中输入存储过程,按“执行”按钮是进行编译,不是执行存储过程。
  如果在存储过程中引用了其他用户的对象,比如表,则必须有其他用户授予的对象访问权限。一个存储过程一旦编译成功,就可以由其他用户或程序来引用。但存储过程或函数的所有者必须授予其他用户执行该过程的权限。
存储过程没有参数,在调用时,直接写过程名即可。
【训练2】  在PL/SQL程序中调用存储过程。
步骤1:登录SCOTT账户。
步骤2:授权STUDENT账户使用该存储过程,即在SQL*Plus输入区中,输入以下的命令:
Sql代码 复制代码
  1. GRANT EXECUTE ON EMP_COUNT TO STUDENT  
  1. GRANT EXECUTE ON EMP_COUNT TO STUDENT  

Sql代码 复制代码
  1. 授权成功。  
  1. 授权成功。  

步骤3:登录STUDENT账户,在SQL*Plus输入区中输入以下程序:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2.         BEGIN  
  3.         SCOTT.EMP_COUNT;   
  4.         END;  
  1. SET SERVEROUTPUT ON  
  2.         BEGIN  
  3.         SCOTT.EMP_COUNT;  
  4.         END;  

步骤4:执行以上程序,结果为:
Sql代码 复制代码
  1. 雇员总人数为:14   
  2.         PL/SQL 过程已成功完成。   
  1. 雇员总人数为:14  
  2.         PL/SQL 过程已成功完成。   

  说明:在本例中,存储过程是由SCOTT账户创建的,STUDEN账户获得SCOTT账户的授权后,才能调用该存储过程。
  注意:在程序中调用存储过程,使用了第二种语法。
【训练3】  编写显示雇员信息的存储过程EMP_LIST,并引用EMP_COUNT存储过程。
步骤1:在SQL*Plus输入区中输入并编译以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE EMP_LIST   
  2.         AS  
  3.          CURSOR emp_cursor IS    
  4.         SELECT empno,ename FROM emp;   
  5.         BEGIN  
  6. FOR Emp_record IN emp_cursor LOOP      
  7.     DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);   
  8.         END LOOP;   
  9.         EMP_COUNT;   
  10.         END;  
  1. CREATE OR REPLACE PROCEDURE EMP_LIST  
  2.         AS  
  3.          CURSOR emp_cursor IS   
  4.         SELECT empno,ename FROM emp;  
  5.         BEGIN  
  6. FOR Emp_record IN emp_cursor LOOP     
  7.     DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);  
  8.         END LOOP;  
  9.         EMP_COUNT;  
  10.         END;  

执行结果:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤2:调用存储过程,在输入区中输入以下语句并执行:
Sql代码 复制代码
  1. EXECUTE EMP_LIST  
  1. EXECUTE EMP_LIST  

显示结果为:
Sql代码 复制代码
  1. 7369SMITH   
  2. 7499ALLEN   
  3. 7521WARD   
  4. 7566JONES   
  5.             执行结果:   
  6.         雇员总人数为:14   
  7.         PL/SQL 过程已成功完成。  
  1. 7369SMITH  
  2. 7499ALLEN  
  3. 7521WARD  
  4. 7566JONES  
  5.             执行结果:  
  6.         雇员总人数为:14  
  7.         PL/SQL 过程已成功完成。  

说明:以上的EMP_LIST存储过程中定义并使用了游标,用来循环显示所有雇员的信息。然后调用已经成功编译的存储过程EMP_COUNT,用来附加显示雇员总人数。通过EXECUTE命令来执行EMP_LIST存储过程。
【练习1】编写显示部门信息的存储过程DEPT_LIST,要求统计出部门个数。
参数传递
参数的作用是向存储过程传递数据,或从存储过程获得返回结果。正确的使用参数可以大大增加存储过程的灵活性和通用性。
参数的类型有三种,如下所示。
Sql代码 复制代码
  1. IN  定义一个输入参数变量,用于传递参数给存储过程   
  2. OUT 定义一个输出参数变量,用于从存储过程获取数据   
  3. IN OUT  定义一个输入、输出参数变量,兼有以上两者的功能  
  1. IN  定义一个输入参数变量,用于传递参数给存储过程  
  2. OUT 定义一个输出参数变量,用于从存储过程获取数据  
  3. IN OUT  定义一个输入、输出参数变量,兼有以上两者的功能  

参数的定义形式和作用如下:
参数名 IN 数据类型 DEFAULT 值;
定义一个输入参数变量,用于传递参数给存储过程。在调用存储过程时,主程序的实际参数可以是常量、有值变量或表达式等。DEFAULT 关键字为可选项,用来设定参数的默认值。如果在调用存储过程时不指明参数,则参数变量取默认值。在存储过程中,输入变量接收主程序传递的值,但不能对其进行赋值。
参数名 OUT 数据类型;
定义一个输出参数变量,用于从存储过程获取数据,即变量从存储过程中返回值给主程序。
在调用存储过程时,主程序的实际参数只能是一个变量,而不能是常量或表达式。在存储过程中,参数变量只能被赋值而不能将其用于赋值,在存储过程中必须给输出变量至少赋值一次。
参数名 IN OUT 数据类型 DEFAULT 值;
定义一个输入、输出参数变量,兼有以上两者的功能。在调用存储过程时,主程序的实际参数只能是一个变量,而不能是常量或表达式。DEFAULT 关键字为可选项,用来设定参数的默认值。在存储过程中,变量接收主程序传递的值,同时可以参加赋值运算,也可以对其进行赋值。在存储过程中必须给变量至少赋值一次。
如果省略IN、OUT或IN OUT,则默认模式是IN。
【训练1】  编写给雇员增加工资的存储过程CHANGE_SALARY,通过IN类型的参数传递要增加工资的雇员编号和增加的工资额。
步骤1:登录SCOTT账户。
  步骤2:在SQL*Plus输入区中输入以下存储过程并执行:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)   
  2.         AS  
  3.          V_ENAME VARCHAR2(10);   
  4. V_SAL NUMBER(5);   
  5.         BEGIN  
  6.         SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;   
  7.          UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;   
  8.          DBMS_OUTPUT.PUT_LINE('雇员'||V_ENAME||'的工资被改为'||TO_CHAR(V_SAL+P_RAISE));   
  9. COMMIT;   
  10.         EXCEPTION   
  11.          WHEN OTHERS THEN  
  12.         DBMS_OUTPUT.PUT_LINE('发生错误,修改失败!');   
  13.         ROLLBACK;   
  14.         END;  
  1. CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)  
  2.         AS  
  3.          V_ENAME VARCHAR2(10);  
  4. V_SAL NUMBER(5);  
  5.         BEGIN  
  6.         SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;  
  7.          UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;  
  8.          DBMS_OUTPUT.PUT_LINE('雇员'||V_ENAME||'的工资被改为'||TO_CHAR(V_SAL+P_RAISE));  
  9. COMMIT;  
  10.         EXCEPTION  
  11.          WHEN OTHERS THEN  
  12.         DBMS_OUTPUT.PUT_LINE('发生错误,修改失败!');  
  13.         ROLLBACK;  
  14.         END;  

执行结果为:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤3:调用存储过程,在输入区中输入以下语句并执行:
Sql代码 复制代码
  1. EXECUTE CHANGE_SALARY(7788,80)  
  1. EXECUTE CHANGE_SALARY(7788,80)  

显示结果为:
Sql代码 复制代码
  1. 雇员SCOTT的工资被改为3080   
  1. 雇员SCOTT的工资被改为3080   

说明:从执行结果可以看到,雇员SCOTT的工资已由原来的3000改为3080。
参数的值由调用者传递,传递的参数的个数、类型和顺序应该和定义的一致。如果顺序不一致,可以采用以下调用方法。如上例,执行语句可以改为:
 EXECUTE CHANGE_SALARY(P_RAISE=>80,P_EMPNO=>7788);
  可以看出传递参数的顺序发生了变化,并且明确指出了参数名和要传递的值,=>运算符左侧是参数名,右侧是参数表达式,这种赋值方法的意义较清楚。
【练习1】创建插入雇员的存储过程INSERT_EMP,并将雇员编号等作为参数。
在设计存储过程的时候,也可以为参数设定默认值,这样调用者就可以不传递或少传递参数了。
【训练2】  调用存储过程CHANGE_SALARY,不传递参数,使用默认参数值。
在SQL*Plus输入区中输入以下命令并执行:
Sql代码 复制代码
  1. EXECUTE CHANGE_SALARY  
  1. EXECUTE CHANGE_SALARY  

显示结果为:
Sql代码 复制代码
  1. 雇员SCOTT的工资被改为3090   
  1. 雇员SCOTT的工资被改为3090   

说明:在存储过程的调用中没有传递参数,而是采用了默认值7788和10,即默认雇员号为7788,增加的工资为10。
【训练3】  使用OUT类型的参数返回存储过程的结果。
步骤1:登录SCOTT账户。
步骤2:在SQL*Plus输入区中输入并编译以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)   
  2.         AS  
  3.         BEGIN  
  4.         SELECT COUNT(*) INTO P_TOTAL FROM EMP;   
  5.         END;  
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)  
  2.         AS  
  3.         BEGIN  
  4.         SELECT COUNT(*) INTO P_TOTAL FROM EMP;  
  5.         END;  

执行结果为:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤3:输入以下程序并执行:
Sql代码 复制代码
  1. DECLARE  
  2.         V_EMPCOUNT NUMBER;   
  3.         BEGIN  
  4.         EMP_COUNT(V_EMPCOUNT);   
  5.         DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_EMPCOUNT);   
  6.         END;  
  1. DECLARE  
  2.         V_EMPCOUNT NUMBER;  
  3.         BEGIN  
  4.         EMP_COUNT(V_EMPCOUNT);  
  5.         DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_EMPCOUNT);  
  6.         END;  

显示结果为:
Sql代码 复制代码
  1. 雇员总人数为:14   
  2.         PL/SQL 过程已成功完成。  
  1. 雇员总人数为:14  
  2.         PL/SQL 过程已成功完成。  

    说明:在存储过程中定义了OUT类型的参数P_TOTAL,在主程序调用该存储过程时,传递了参数V_EMPCOUNT。在存储过程中的SELECT...INTO...语句中对P_TOTAL进行赋值,赋值结果由V_EMPCOUNT变量带回给主程序并显示。
以上程序要覆盖同名的EMP_COUNT存储过程,如果不使用OR REPLACE选项,就会出现以下错误:
Sql代码 复制代码
  1. ERROR 位于第 1 行:   
  2.         ORA-00955: 名称已由现有对象使用。  
  1. ERROR 位于第 1 行:  
  2.         ORA-00955: 名称已由现有对象使用。  

【练习2】创建存储过程,使用OUT类型参数获得雇员经理名。
【训练4】  使用IN OUT类型的参数,给电话号码增加区码。
步骤1:登录SCOTT账户。
步骤2:在SQL*Plus输入区中输入并编译以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)   
  2.         AS  
  3.         BEGIN  
  4.          P_HPONE_NUM:='0755-'||P_HPONE_NUM;   
  5.         END;  
  1. CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)  
  2.         AS  
  3.         BEGIN  
  4.          P_HPONE_NUM:='0755-'||P_HPONE_NUM;  
  5.         END;  

执行结果为:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤3:输入以下程序并执行:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3. V_PHONE_NUM VARCHAR2(15);   
  4. BEGIN  
  5. V_PHONE_NUM:='26731092';   
  6. ADD_REGION(V_PHONE_NUM);   
  7. DBMS_OUTPUT.PUT_LINE('新的电话号码:'||V_PHONE_NUM);   
  8. END;  
  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3. V_PHONE_NUM VARCHAR2(15);  
  4. BEGIN  
  5. V_PHONE_NUM:='26731092';  
  6. ADD_REGION(V_PHONE_NUM);  
  7. DBMS_OUTPUT.PUT_LINE('新的电话号码:'||V_PHONE_NUM);  
  8. END;  

显示结果为:
Sql代码 复制代码
  1. 新的电话号码:0755-26731092   
  2.         PL/SQL 过程已成功完成。  
  1. 新的电话号码:0755-26731092  
  2.         PL/SQL 过程已成功完成。  

说明:变量V_HPONE_NUM既用来向存储过程传递旧电话号码,也用来向主程序返回新号码。新的号码在原来基础上增加了区号0755和-。
创建和删除存储函数
  创建函数,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系统权限。该权限可由系统管理员授予。创建存储函数的语法和创建存储过程的类似,即
CREATE [OR REPLACE] FUNCTION 函数名[(参数[IN] 数据类型...)]
RETURN 数据类型
{AS|IS}
[说明部分]
BEGIN
可执行部分
RETURN (表达式)
[EXCEPTION
    错误处理部分]
END [函数名];
其中,参数是可选的,但只能是IN类型(IN关键字可以省略)。
在定义部分的RETURN 数据类型,用来表示函数的数据类型,也就是返回值的类型,此部分不可省略。
在可执行部分的RETURN(表达式),用来生成函数的返回值,其表达式的类型应该和定义部分说明的函数返回值的数据类型一致。在函数的执行部分可以有多个RETURN语句,但只有一个RETURN语句会被执行,一旦执行了RETURN语句,则函数结束并返回调用环境。
一个存储函数在不需要时可以删除,但删除的人应是函数的创建者或者是拥有DROP ANY PROCEDURE系统权限的人。其语法如下:
DROP FUNCTION 函数名;
重新编译一个存储函数时,编译的人应是函数的创建者或者拥有ALTER ANY PROCEDURE系统权限的人。重新编译一个存储函数的语法如下:
ALTER PROCEDURE 函数名 COMPILE;
函数的调用者应是函数的创建者或拥有EXECUTE ANY PROCEDURE系统权限的人,或是被函数的拥有者授予了函数执行权限的账户。函数的引用和存储过程不同,函数要出现在程序体中,可以参加表达式的运算或单独出现在表达式中,其形式如下:
变量名:=函数名(...)
【训练1】  创建一个通过雇员编号返回雇员名称的函数GET_EMP_NAME。
步骤1:登录SCOTT账户。
步骤2:在SQL*Plus输入区中输入以下存储函数并编译:
Sql代码 复制代码
  1. CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)   
  2.         RETURN VARCHAR2   
  3.         AS  
  4.          V_ENAME VARCHAR2(10);   
  5.         BEGIN  
  6.         ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;   
  7. RETURN(V_ENAME);   
  8. EXCEPTION   
  9.  WHEN NO_DATA_FOUND THEN  
  10.   DBMS_OUTPUT.PUT_LINE('没有该编号雇员!');   
  11.   RETURN (NULL);   
  12.  WHEN TOO_MANY_ROWS THEN  
  13.   DBMS_OUTPUT.PUT_LINE('有重复雇员编号!');   
  14.   RETURN (NULL);   
  15.  WHEN OTHERS THEN  
  16.   DBMS_OUTPUT.PUT_LINE('发生其他错误!');   
  17.   RETURN (NULL);   
  18. END;  
  1. CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)  
  2.         RETURN VARCHAR2  
  3.         AS  
  4.          V_ENAME VARCHAR2(10);  
  5.         BEGIN  
  6.         ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;  
  7. RETURN(V_ENAME);  
  8. EXCEPTION  
  9.  WHEN NO_DATA_FOUND THEN  
  10.   DBMS_OUTPUT.PUT_LINE('没有该编号雇员!');  
  11.   RETURN (NULL);  
  12.  WHEN TOO_MANY_ROWS THEN  
  13.   DBMS_OUTPUT.PUT_LINE('有重复雇员编号!');  
  14.   RETURN (NULL);  
  15.  WHEN OTHERS THEN  
  16.   DBMS_OUTPUT.PUT_LINE('发生其他错误!');  
  17.   RETURN (NULL);  
  18. END;  

步骤3:调用该存储函数,输入并执行以下程序:
Sql代码 复制代码
  1. BEGIN  
  2.         DBMS_OUTPUT.PUT_LINE('雇员7369的名称是:'|| GET_EMP_NAME(7369));   
  3.          DBMS_OUTPUT.PUT_LINE('雇员7839的名称是:'|| GET_EMP_NAME(7839));   
  4.         END;  
  1. BEGIN  
  2.         DBMS_OUTPUT.PUT_LINE('雇员7369的名称是:'|| GET_EMP_NAME(7369));  
  3.          DBMS_OUTPUT.PUT_LINE('雇员7839的名称是:'|| GET_EMP_NAME(7839));  
  4.         END;  

显示结果为:
Sql代码 复制代码
  1. 雇员7369的名称是:SMITH   
  2.         雇员7839的名称是:KING   
  3.         PL/SQL 过程已成功完成。  
  1. 雇员7369的名称是:SMITH  
  2.         雇员7839的名称是:KING  
  3.         PL/SQL 过程已成功完成。  

说明:函数的调用直接出现在程序的DBMS_OUTPUT.PUT_LINE语句中,作为字符串表达式的一部分。如果输入了错误的雇员编号,就会在函数的错误处理部分输出错误信息。试修改雇员编号,重新运行调用部分。
【练习1】创建一个通过部门编号返回部门名称的存储函数GET_DEPT_NAME。
   【练习2】将函数的执行权限授予STUDENT账户,然后登录STUDENT账户调用。
存储过程和函数的查看
可以通过对数据字典的访问来查询存储过程或函数的有关信息,如果要查询当前用户的存储过程或函数的源代码,可以通过对USER_SOURCE数据字典视图的查询得到。USER_SOURCE的结构如下:
Sql代码 复制代码
  1. DESCRIBE USER_SOURCE  
  1. DESCRIBE USER_SOURCE  

结果为:
Sql代码 复制代码
  1. 名称                                      是否为空? 类型   
  2.         ------------------------------------------------------------- ------------- -----------------------  
  3.  NAME                                               VARCHAR2(30)   
  4.  TYPE                                               VARCHAR2(12)   
  5.  LINE                                               NUMBER   
  6.  TEXT                                               VARCHAR2(4000)  
  1. 名称                                      是否为空? 类型  
  2.         ------------------------------------------------------------- ------------- -----------------------  
  3.  NAME                                               VARCHAR2(30)  
  4.  TYPE                                               VARCHAR2(12)  
  5.  LINE                                               NUMBER  
  6.  TEXT                                               VARCHAR2(4000)  

  说明:里面按行存放着过程或函数的脚本,NAME是过程或函数名,TYPE 代表类型(PROCEDURE或FUNCTION),LINE是行号,TEXT 为脚本。
【训练1】  查询过程EMP_COUNT的脚本。
在SQL*Plus中输入并执行如下查询:
Sql代码 复制代码
  1. select TEXT  from user_source WHERE NAME='EMP_COUNT';  
  1. select TEXT  from user_source WHERE NAME='EMP_COUNT';  

结果为:
Sql代码 复制代码
  1. TEXT   
  2. --------------------------------------------------------------------------------  
  3. PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)   
  4. AS  
  5. BEGIN  
  6.  SELECT COUNT(*) INTO P_TOTAL FROM EMP;   
  7. END;  
  1. TEXT  
  2. --------------------------------------------------------------------------------  
  3. PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)  
  4. AS  
  5. BEGIN  
  6.  SELECT COUNT(*) INTO P_TOTAL FROM EMP;  
  7. END;  

【训练2】  查询过程GET_EMP_NAME的参数。
在SQL*Plus中输入并执行如下查询:
Sql代码 复制代码
  1. DESCRIBE GET_EMP_NAME  
  1. DESCRIBE GET_EMP_NAME  

结果为:
Sql代码 复制代码
  1. FUNCTION GET_EMP_NAME RETURNS VARCHAR2   
  2.         参数名称            类型          输入/输出默认值?   
  3.         ----------------------------------------- ----------------------------------- ----------------- -------------  
  4.         P_EMPNO             NUMBER(4) IN     DEFAULT  
  1. FUNCTION GET_EMP_NAME RETURNS VARCHAR2  
  2.         参数名称            类型          输入/输出默认值?  
  3.         ----------------------------------------- ----------------------------------- ----------------- -------------  
  4.         P_EMPNO             NUMBER(4) IN     DEFAULT  

【训练3】  在发生编译错误时,显示错误。
Sql代码 复制代码
  1. SHOW ERRORS  
  1. SHOW ERRORS  

以下是一段编译错误显示:
Sql代码 复制代码
  1. LINE/COL ERROR   
  2.         ------------- -----------------------------------------------------------------  
  3.         4/2       PL/SQL: SQL Statement ignored   
  4.         4/36      PLS-00201: 必须说明标识符 'EMPP'  
  1. LINE/COL ERROR  
  2.         ------------- -----------------------------------------------------------------  
  3.         4/2       PL/SQL: SQL Statement ignored  
  4.         4/36      PLS-00201: 必须说明标识符 'EMPP'  

  说明:查询一个存储过程或函数是否是有效状态(即编译成功),可以使用数据字典USER_OBJECTS的STATUS列。
【训练4】  查询EMP_LIST存储过程是否可用:
Sql代码 复制代码
  1. SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME='EMP_LIST';  
  1. SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME='EMP_LIST';  

结果为:
Sql代码 复制代码
  1. STATUS   
  2.         ------------  
  3.         VALID  
  1. STATUS  
  2.         ------------  
  3.         VALID  

说明:VALID表示该存储过程有效(即通过编译),INVALID表示存储过程无效或需要重新编译。当Oracle调用一个无效的存储过程或函数时,首先试图对其进行编译,如果编译成功则将状态置成VALID并执行,否则给出错误信息。
当一个存储过程编译成功,状态变为VALID,会不会在某些情况下变成INVALID。结论是完全可能的。比如一个存储过程中包含对表的查询,如果表被修改或删除,存储过程就会变成无效INVALID。所以要注意存储过程和函数对其他对象的依赖关系。
如果要检查存储过程或函数的依赖性,可以通过查询数据字典USER_DENPENDENCIES来确定,该表结构如下:
Sql代码 复制代码
  1. DESCRIBE USER_DEPENDENCIES;  
  1. DESCRIBE USER_DEPENDENCIES;  

结果:
Sql代码 复制代码
  1. 名称                     是否为空? 类型   
  2.         -------------------------------------------------------------- ------------- ----------------------------  
  3.          NAME            NOT NULL   VARCHAR2(30)   
  4.          TYPE                       VARCHAR2(12)   
  5.         REFERENCED_OWNER                                VARCHAR2(30)   
  6.  REFERENCED_NAME                                VARCHAR2(64)   
  7.  REFERENCED_TYPE                                VARCHAR2(12)   
  8. REFERENCED_LINK_NAME                            VARCHAR2(128)   
  9.         SCHEMAID                                        NUMBER   
  10.          DEPENDENCY_TYPE                                VARCHAR2(4)  
  1. 名称                     是否为空? 类型  
  2.         -------------------------------------------------------------- ------------- ----------------------------  
  3.          NAME            NOT NULL   VARCHAR2(30)  
  4.          TYPE                       VARCHAR2(12)  
  5.         REFERENCED_OWNER                                VARCHAR2(30)  
  6.  REFERENCED_NAME                                VARCHAR2(64)  
  7.  REFERENCED_TYPE                                VARCHAR2(12)  
  8. REFERENCED_LINK_NAME                            VARCHAR2(128)  
  9.         SCHEMAID                                        NUMBER  
  10.          DEPENDENCY_TYPE                                VARCHAR2(4)  

  说明:NAME为实体名,TYPE为实体类型,REFERENCED_OWNER为涉及到的实体拥有者账户,REFERENCED_NAME为涉及到的实体名,REFERENCED_TYPE 为涉及到的实体类型。
【训练5】  查询EMP_LIST存储过程的依赖性。
Sql代码 复制代码
  1. SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME='EMP_LIST';  
  1. SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME='EMP_LIST';  

执行结果:
Sql代码 复制代码
  1. REFERENCED_NAME                                         REFERENCED_TYPE   
  2.         ------------------------------------------------------------------------------------------ ----------------------------  
  3. STANDARD                                                PACKAGE   
  4.         SYS_STUB_FOR_PURITY_ANALYSIS                            PACKAGE   
  5.         DBMS_OUTPUT                                                 PACKAGE   
  6.         DBMS_OUTPUT                                             SYNONYM   
  7. DBMS_OUTPUT                      NON-EXISTENT   
  8.         EMP                                                         TABLE  
  9.         EMP_COUNT                                                   PROCEDURE  
  1. REFERENCED_NAME                                         REFERENCED_TYPE  
  2.         ------------------------------------------------------------------------------------------ ----------------------------  
  3. STANDARD                                                PACKAGE  
  4.         SYS_STUB_FOR_PURITY_ANALYSIS                            PACKAGE  
  5.         DBMS_OUTPUT                                                 PACKAGE  
  6.         DBMS_OUTPUT                                             SYNONYM  
  7. DBMS_OUTPUT                      NON-EXISTENT  
  8.         EMP                                                         TABLE  
  9.         EMP_COUNT                                                   PROCEDURE  

  说明:可以看出存储过程EMP_LIST依赖一些系统包、EMP表和EMP_COUNT存储过程。如果删除了EMP表或EMP_COUNT存储过程,EMP_LIST将变成无效。
还有一种情况需要我们注意:如果一个用户A被授予执行属于用户B的一个存储过程的权限,在用户B的存储过程中,访问到用户C的表,用户B被授予访问用户C的表的权限,但用户A没有被授予访问用户C表的权限,那么用户A调用用户B的存储过程是失败的还是成功的呢?答案是成功的。如果读者有兴趣,不妨进行一下实际测试。


包的概念和组成
包是用来存储相关程序结构的对象,它存储于数据字典中。包由两个分离的部分组成:包头(PACKAGE)和包体(PACKAGE BODY)。包头是包的说明部分,是对外的操作接口,对应用是可见的;包体是包的代码和实现部分,对应用来说是不可见的黑盒。
包中可以包含的程序结构如下所示。
Sql代码 复制代码
  1. 过程(PROCUDURE)   带参数的命名的程序模块   
  2. 函数(FUNCTION)    带参数、具有返回值的命名的程序模块   
  3. 变量(VARIABLE)    存储变化的量的存储单元   
  4. 常量(CONSTANT)    存储不变的量的存储单元   
  5. 游标(CURSOR)  用户定义的数据操作缓存区,在可执行部分使用   
  6. 类型(TYPE)    用户定义的新的结构类型   
  7. 异常(EXCEPTION)   在标准包中定义或由用户自定义,用于处理程序错误  
  1. 过程(PROCUDURE)   带参数的命名的程序模块  
  2. 函数(FUNCTION)    带参数、具有返回值的命名的程序模块  
  3. 变量(VARIABLE)    存储变化的量的存储单元  
  4. 常量(CONSTANT)    存储不变的量的存储单元  
  5. 游标(CURSOR)  用户定义的数据操作缓存区,在可执行部分使用  
  6. 类型(TYPE)    用户定义的新的结构类型  
  7. 异常(EXCEPTION)   在标准包中定义或由用户自定义,用于处理程序错误  

说明部分可以出现在包的三个不同的部分:出现在包头中的称为公有元素,出现在包体中的称为私有元素,出现在包体的过程(或函数)中的称为局部变量。它们的性质有所不同,如下所示。
Sql代码 复制代码
  1. 公有元素(PUBLIC)    在包头中说明,在包体中具体定义 在包外可见并可以访问,对整个应用的全过程有效   
  2. 私有元素(PRIVATE)   在包体的说明部分说明  只能被包内部的其他部分访问   
  3. 局部变量(LOCAL) 在过程或函数的说明部分说明   只能在定义变量的过程或函数中使用  
  1. 公有元素(PUBLIC)    在包头中说明,在包体中具体定义 在包外可见并可以访问,对整个应用的全过程有效  
  2. 私有元素(PRIVATE)   在包体的说明部分说明  只能被包内部的其他部分访问  
  3. 局部变量(LOCAL) 在过程或函数的说明部分说明   只能在定义变量的过程或函数中使用  

在包体中出现的过程或函数,如果需要对外公用,就必须在包头中说明,包头中的说明应该和包体中的说明一致。
包有以下优点:
* 包可以方便地将存储过程和函数组织到一起,每个包又是相互独立的。在不同的包中,过程、函数都可以重名,这解决了在同一个用户环境中命名的冲突问题。
* 包增强了对存储过程和函数的安全管理,对整个包的访问权只需一次授予。
  * 在同一个会话中,公用变量的值将被保留,直到会话结束。
* 区分了公有过程和私有过程,包体的私有过程增加了过程和函数的保密性。
* 包在被首次调用时,就作为一个整体被全部调入内存,减少了多次访问过程或函数的I/O次数。
创建包和包体
包由包头和包体两部分组成,包的创建应该先创建包头部分,然后创建包体部分。创建、删除和编译包的权限同创建、删除和编译存储过程的权限相同。
创建包头的简要语句如下:
CREATE [OR REPLACE] PACKAGE 包名
{IS|AS}
公有变量定义
公有类型定义
公有游标定义
公有异常定义
函数说明
过程说明
END;
创建包体的简要语法如下:
CREATE [OR REPLACE] PACKAGE BODY 包名
{IS|AS}
私有变量定义
私有类型定义
私有游标定义
私有异常定义
函数定义
过程定义
END;
包的其他操作命令包括:
删除包头:
DROP PACKAGE 包头名
删除包体:
DROP PACKAGE BODY 包体名
重新编译包头:
ALTER PACKAGE 包名 COMPILE PACKAGE
重新编译包体:
ALTER PACKAGE 包名 COMPILE PACKAGE BODY
在包头中说明的对象可以在包外调用,调用的方法和调用单独的过程或函数的方法基本相同,惟一的区别就是要在调用的过程或函数名前加上包的名字(中间用“.”分隔)。但要注意,不同的会话将单独对包的公用变量进行初始化,所以不同的会话对包的调用属于不同的应用。
系统包
Oracle预定义了很多标准的系统包,这些包可以在应用中直接使用,比如在训练中我们使用的DBMS_OUTPUT包,就是系统包。PUT_LINE是该包的一个函数。常用系统包下所示。
Sql代码 复制代码
  1. DBMS_OUTPUT 在SQL*Plus环境下输出信息   
  2. DBMS_DDL    编译过程函数和包   
  3. DBMS_SESSION    改变用户的会话,初始化包等   
  4. DBMS_TRANSACTION    控制数据库事务   
  5. DBMS_MAIL   连接Oracle*Mail   
  6. DBMS_LOCK   进行复杂的锁机制管理   
  7. DBMS_ALERT  识别数据库事件告警   
  8. DBMS_PIPE   通过管道在会话间传递信息   
  9. DBMS_JOB    管理Oracle的作业   
  10. DBMS_LOB    操纵大对象   
  11. DBMS_SQL    执行动态SQL语句  
  1. DBMS_OUTPUT 在SQL*Plus环境下输出信息  
  2. DBMS_DDL    编译过程函数和包  
  3. DBMS_SESSION    改变用户的会话,初始化包等  
  4. DBMS_TRANSACTION    控制数据库事务  
  5. DBMS_MAIL   连接Oracle*Mail  
  6. DBMS_LOCK   进行复杂的锁机制管理  
  7. DBMS_ALERT  识别数据库事件告警  
  8. DBMS_PIPE   通过管道在会话间传递信息  
  9. DBMS_JOB    管理Oracle的作业  
  10. DBMS_LOB    操纵大对象  
  11. DBMS_SQL    执行动态SQL语句  

包的应用
在SQL*Plus环境下,包和包体可以分别编译,也可以一起编译。如果分别编译,则要先编译包头,后编译包体。如果在一起编译,则包头写在前,包体在后,中间用“/”分隔。
可以将已经存在的存储过程或函数添加到包中,方法是去掉过程或函数创建语句的CREATE OR REPLACE部分,将存储过程或函数复制到包体中 ,然后重新编译即可。
   如果需要将私有过程或函数变成共有过程或函数的话,将过程或函数说明部分复制到包头说明部分,然后重新编译就可以了。
【训练1】  创建管理雇员信息的包EMPLOYE,它具有从EMP表获得雇员信息,修改雇员名称,修改雇员工资和写回EMP表的功能。
步骤1:登录SCOTT账户,输入以下代码并编译:
Sql代码 复制代码
  1. CREATE OR REPLACE PACKAGE EMPLOYE --包头部分   
  2.         IS  
  3.  PROCEDURE SHOW_DETAIL;    
  4.  PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);    
  5.  PROCEDURE SAVE_EMPLOYE;    
  6.  PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);    
  7. PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);    
  8.         END EMPLOYE;   
  9.         /   
  10.         CREATE OR REPLACE PACKAGE BODY EMPLOYE --包体部分   
  11.         IS  
  12.  EMPLOYE EMP%ROWTYPE;   
  13.         -------------- 显示雇员信息 ---------------  
  14.         PROCEDURE SHOW_DETAIL   
  15.         AS  
  16.         BEGIN  
  17. DBMS_OUTPUT.PUT_LINE(‘----- 雇员信息 -----’);     
  18.         DBMS_OUTPUT.PUT_LINE('雇员编号:'||EMPLOYE.EMPNO);   
  19.         DBMS_OUTPUT.PUT_LINE('雇员名称:'||EMPLOYE.ENAME);   
  20.           DBMS_OUTPUT.PUT_LINE('雇员职务:'||EMPLOYE.JOB);   
  21.          DBMS_OUTPUT.PUT_LINE('雇员工资:'||EMPLOYE.SAL);   
  22.          DBMS_OUTPUT.PUT_LINE('部门编号:'||EMPLOYE.DEPTNO);   
  23.         END SHOW_DETAIL;   
  24. ----------------- 从EMP表取得一个雇员 --------------------  
  25.          PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)   
  26.         AS  
  27.         BEGIN  
  28.         SELECT * INTO EMPLOYE FROM EMP WHERE    EMPNO=P_EMPNO;   
  29.         DBMS_OUTPUT.PUT_LINE('获取雇员'||EMPLOYE.ENAME||'信息成功');   
  30.          EXCEPTION   
  31.          WHEN OTHERS THEN  
  32.            DBMS_OUTPUT.PUT_LINE('获取雇员信息发生错误!');   
  33.         END GET_EMPLOYE;   
  34. ---------------------- 保存雇员到EMP表 --------------------------  
  35.         PROCEDURE SAVE_EMPLOYE   
  36.         AS  
  37.         BEGIN  
  38.         UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=   
  39.     EMPLOYE.EMPNO;   
  40.      DBMS_OUTPUT.PUT_LINE('雇员信息保存完成!');   
  41.         END SAVE_EMPLOYE;   
  42. ---------------------------- 修改雇员名称 ------------------------------  
  43.         PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)   
  44.          AS  
  45.         BEGIN  
  46.          EMPLOYE.ENAME:=P_NEWNAME;   
  47.          DBMS_OUTPUT.PUT_LINE('修改名称完成!');   
  48.         END CHANGE_NAME;   
  49. ---------------------------- 修改雇员工资 --------------------------  
  50.         PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)   
  51.         AS  
  52.         BEGIN  
  53.          EMPLOYE.SAL:=P_NEWSAL;   
  54.          DBMS_OUTPUT.PUT_LINE('修改工资完成!');   
  55.         END CHANGE_SAL;   
  56.         END EMPLOYE;  
  1. CREATE OR REPLACE PACKAGE EMPLOYE --包头部分   
  2.         IS  
  3.  PROCEDURE SHOW_DETAIL;   
  4.  PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);   
  5.  PROCEDURE SAVE_EMPLOYE;   
  6.  PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);   
  7. PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);   
  8.         END EMPLOYE;  
  9.         /  
  10.         CREATE OR REPLACE PACKAGE BODY EMPLOYE --包体部分   
  11.         IS  
  12.  EMPLOYE EMP%ROWTYPE;  
  13.         -------------- 显示雇员信息 ---------------  
  14.         PROCEDURE SHOW_DETAIL  
  15.         AS  
  16.         BEGIN  
  17. DBMS_OUTPUT.PUT_LINE(‘----- 雇员信息 -----’);     
  18.         DBMS_OUTPUT.PUT_LINE('雇员编号:'||EMPLOYE.EMPNO);  
  19.         DBMS_OUTPUT.PUT_LINE('雇员名称:'||EMPLOYE.ENAME);  
  20.           DBMS_OUTPUT.PUT_LINE('雇员职务:'||EMPLOYE.JOB);  
  21.          DBMS_OUTPUT.PUT_LINE('雇员工资:'||EMPLOYE.SAL);  
  22.          DBMS_OUTPUT.PUT_LINE('部门编号:'||EMPLOYE.DEPTNO);  
  23.         END SHOW_DETAIL;  
  24. ----------------- 从EMP表取得一个雇员 --------------------  
  25.          PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)  
  26.         AS  
  27.         BEGIN  
  28.         SELECT * INTO EMPLOYE FROM EMP WHERE    EMPNO=P_EMPNO;  
  29.         DBMS_OUTPUT.PUT_LINE('获取雇员'||EMPLOYE.ENAME||'信息成功');  
  30.          EXCEPTION  
  31.          WHEN OTHERS THEN  
  32.            DBMS_OUTPUT.PUT_LINE('获取雇员信息发生错误!');  
  33.         END GET_EMPLOYE;  
  34. ---------------------- 保存雇员到EMP表 --------------------------  
  35.         PROCEDURE SAVE_EMPLOYE  
  36.         AS  
  37.         BEGIN  
  38.         UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=  
  39.     EMPLOYE.EMPNO;  
  40.      DBMS_OUTPUT.PUT_LINE('雇员信息保存完成!');  
  41.         END SAVE_EMPLOYE;  
  42. ---------------------------- 修改雇员名称 ------------------------------  
  43.         PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)  
  44.          AS  
  45.         BEGIN  
  46.          EMPLOYE.ENAME:=P_NEWNAME;  
  47.          DBMS_OUTPUT.PUT_LINE('修改名称完成!');  
  48.         END CHANGE_NAME;  
  49. ---------------------------- 修改雇员工资 --------------------------  
  50.         PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)  
  51.         AS  
  52.         BEGIN  
  53.          EMPLOYE.SAL:=P_NEWSAL;  
  54.          DBMS_OUTPUT.PUT_LINE('修改工资完成!');  
  55.         END CHANGE_SAL;  
  56.         END EMPLOYE;  

步骤2:获取雇员7788的信息:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE EMPLOYE.GET_EMPLOYE(7788);  
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE EMPLOYE.GET_EMPLOYE(7788);  

结果为:
Sql代码 复制代码
  1. 获取雇员SCOTT信息成功   
  2.         PL/SQL 过程已成功完成。  
  1. 获取雇员SCOTT信息成功  
  2.         PL/SQL 过程已成功完成。  

步骤3:显示雇员信息:
Sql代码 复制代码
  1. EXECUTE EMPLOYE.SHOW_DETAIL;  
  1. EXECUTE EMPLOYE.SHOW_DETAIL;  

结果为:
Sql代码 复制代码
  1. ------------------ 雇员信息 ------------------  
  2.         雇员编号:7788   
  3.         雇员名称:SCOTT   
  4.         雇员职务:ANALYST   
  5.         雇员工资:3000   
  6.         部门编号:20   
  7.         PL/SQL 过程已成功完成。  
  1. ------------------ 雇员信息 ------------------  
  2.         雇员编号:7788  
  3.         雇员名称:SCOTT  
  4.         雇员职务:ANALYST  
  5.         雇员工资:3000  
  6.         部门编号:20  
  7.         PL/SQL 过程已成功完成。  

步骤4:修改雇员工资:
Sql代码 复制代码
  1. EXECUTE EMPLOYE.CHANGE_SAL(3800);  
  1. EXECUTE EMPLOYE.CHANGE_SAL(3800);  

结果为:
Sql代码 复制代码
  1. 修改工资完成!   
  2.         PL/SQL 过程已成功完成。  
  1. 修改工资完成!  
  2.         PL/SQL 过程已成功完成。  

步骤5:将修改的雇员信息存入EMP表
Sql代码 复制代码
  1. EXECUTE EMPLOYE.SAVE_EMPLOYE;  
  1. EXECUTE EMPLOYE.SAVE_EMPLOYE;  

结果为:
Sql代码 复制代码
  1. 雇员信息保存完成!   
  2.         PL/SQL 过程已成功完成。  
  1. 雇员信息保存完成!  
  2.         PL/SQL 过程已成功完成。  

说明:该包完成将EMP表中的某个雇员的信息取入内存记录变量,在记录变量中进行修改编辑,在确认显示信息正确后写回EMP表的功能。记录变量EMPLOYE用来存储取得的雇员信息,定义为私有变量,只能被包的内部模块访问。
  【练习1】为包增加修改雇员职务和部门编号的功能。

阶段训练
下面的训练通过定义和创建完整的包EMP_PK并综合运用本章的知识,完成对雇员表的插入、删除等功能,包中的主要元素解释如下所示。
Sql代码 复制代码
  1. 程序结构    类  型    说    明   
  2. V_EMP_COUNT 公有变量    跟踪雇员的总人数变化,插入、删除雇员的同时修改该变量的值   
  3. INIT    公有过程    对包进行初始化,初始化雇员人数和工资修改的上、下限   
  4. LIST_EMP    公有过程    显示雇员列表   
  5. INSERT_EMP  公有过程    通过编号插入新雇员   
  6. DELETE_EMP  公有过程    通过编号删除雇员   
  7. CHANGE_EMP_SAL  公有过程    通过编号修改雇员工资   
  8. V_MESSAGE   私有变量    存放准备输出的信息   
  9. C_MAX_SAL   私有变量    对工资修改的上限   
  10. C_MIN_SAL   私有变量    对工资修改的下限   
  11. SHOW_MESSAGE    私有过程    显示私有变量V_MESSAGE中的信息   
  12. EXIST_EMP   私有函数    判断某个编号的雇员是否存在,该函数被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等过程调用  
  1. 程序结构    类  型    说    明  
  2. V_EMP_COUNT 公有变量    跟踪雇员的总人数变化,插入、删除雇员的同时修改该变量的值  
  3. INIT    公有过程    对包进行初始化,初始化雇员人数和工资修改的上、下限  
  4. LIST_EMP    公有过程    显示雇员列表  
  5. INSERT_EMP  公有过程    通过编号插入新雇员  
  6. DELETE_EMP  公有过程    通过编号删除雇员  
  7. CHANGE_EMP_SAL  公有过程    通过编号修改雇员工资  
  8. V_MESSAGE   私有变量    存放准备输出的信息  
  9. C_MAX_SAL   私有变量    对工资修改的上限  
  10. C_MIN_SAL   私有变量    对工资修改的下限  
  11. SHOW_MESSAGE    私有过程    显示私有变量V_MESSAGE中的信息  
  12. EXIST_EMP   私有函数    判断某个编号的雇员是否存在,该函数被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等过程调用  

【训练1】  完整的雇员包EMP_PK的创建和应用。
步骤1:在SQL*Plus中登录SCOTT账户,输入以下包头和包体部分,按“执行”按钮编译:
Sql代码 复制代码
  1. CREATE OR REPLACE PACKAGE EMP_PK    
  2.         --包头部分   
  3.         IS  
  4.         V_EMP_COUNT NUMBER(5);                 
  5.         --雇员人数  
  6.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER);  --初始化  
  7.         PROCEDURE LIST_EMP;                        
  8.         --显示雇员列表  
  9. PROCEDURE INSERT_EMP(P_EMPNO        NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,   
  10.         P_SAL NUMBER);                         
  11.         --插入雇员  
  12.         PROCEDURE DELETE_EMP(P_EMPNO NUMBER);       --删除雇员  
  13.          PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);    
  14.         --修改雇员工资  
  15.         END EMP_PK;   
  16.         /CREATE OR REPLACE PACKAGE BODY EMP_PK   
  17.          --包体部分   
  18.         IS  
  19.         V_MESSAGE VARCHAR2(50); --显示信息  
  20. V_MAX_SAL NUMBER(7); --工资上限  
  21.         V_MIN_SAL NUMBER(7); --工资下限  
  22.         FUNCTION EXIST_EMP(P_EMPNO NUMBER)  RETURN  BOOLEAN; --判断雇员是否存在函数  
  23.         PROCEDURE SHOW_MESSAGE; --显示信息过程  
  24.         ------------------------------- 初始化过程 ----------------------------  
  25.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)    
  26.         IS    
  27.         BEGIN  
  28.          SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;   
  29. V_MAX_SAL:=P_MAX;   
  30.          V_MIN_SAL:=P_MIN;   
  31.          V_MESSAGE:='初始化过程已经完成!';   
  32.          SHOW_MESSAGE;    
  33.         END INIT;   
  34. ---------------------------- 显示雇员列表过程 ---------------------  
  35.         PROCEDURE LIST_EMP    
  36.          IS    
  37.         BEGIN  
  38. DBMS_OUTPUT.PUT_LINE('姓名       职务      工资');   
  39.         FOR emp_rec IN (SELECT * FROM EMP)   
  40.         LOOP   
  41.     DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,'')||RPAD(emp_rec.job,10,' ')||TO_CHAR(emp_rec.sal));   
  42.          END LOOP;   
  43.          DBMS_OUTPUT.PUT_LINE('雇员总人数'||V_EMP_COUNT);   
  44.         END LIST_EMP;   
  45. ----------------------------- 插入雇员过程 -----------------------------  
  46.         PROCEDUREINSERT_EMP(P_EMPNO     NUMBER,P_ENAMEVARCHAR2,P_JOB    VARCHAR2,P_SAL NUMBER)   
  47.          IS    
  48.         BEGIN  
  49.         IF NOT EXIST_EMP(P_EMPNO) THEN  
  50.         INSERT INTO EMP(EMPNO,ENAME,JOB,SAL)        VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);   
  51.         COMMIT;    
  52.         V_EMP_COUNT:=V_EMP_COUNT+1;   
  53.         V_MESSAGE:='雇员'||P_EMPNO||'已插入!';   
  54.         ELSE  
  55. V_MESSAGE:='雇员'||P_EMPNO||'已存在,不能插入!';   
  56.       END IF;   
  57.      SHOW_MESSAGE;    
  58.      EXCEPTION   
  59.     WHEN OTHERS THEN  
  60.      V_MESSAGE:='雇员'||P_EMPNO||'插入失败!';   
  61.      SHOW_MESSAGE;   
  62.      END INSERT_EMP;   
  63. --------------------------- 删除雇员过程 --------------------  
  64.          PROCEDURE DELETE_EMP(P_EMPNO NUMBER)    
  65.         IS    
  66.         BEGIN    
  67.         IF EXIST_EMP(P_EMPNO) THEN  
  68.         DELETE FROM EMP WHERE EMPNO=P_EMPNO;   
  69.         COMMIT;   
  70.          V_EMP_COUNT:=V_EMP_COUNT-1;   
  71.          V_MESSAGE:='雇员'||P_EMPNO||'已删除!';   
  72.          ELSE  
  73. V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能删除!';   
  74.     END IF;   
  75.     SHOW_MESSAGE;   
  76.      EXCEPTION   
  77.      WHEN OTHERS THEN  
  78.      V_MESSAGE:='雇员'||P_EMPNO||'删除失败!';   
  79.      SHOW_MESSAGE;   
  80.     END DELETE_EMP;   
  81. --------------------------------------- 修改雇员工资过程 ------------------------------------  
  82.         PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)    
  83.          IS    
  84.          BEGIN    
  85.          IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN  
  86.          V_MESSAGE:='工资超出修改范围!';   
  87.         ELSIF NOT EXIST_EMP(P_EMPNO) THEN  
  88.         V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能修改工资!';   
  89. ELSE  
  90.          UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;   
  91.         COMMIT;   
  92.         V_MESSAGE:='雇员'||P_EMPNO||'工资已经修改!';   
  93.         END IF;   
  94.         SHOW_MESSAGE;   
  95.         EXCEPTION   
  96.          WHEN OTHERS THEN  
  97.          V_MESSAGE:='雇员'||P_EMPNO||'工资修改失败!';   
  98.          SHOW_MESSAGE;   
  99.          END CHANGE_EMP_SAL;   
  100. ---------------------------- 显示信息过程 ----------------------------  
  101.          PROCEDURE SHOW_MESSAGE    
  102.         IS    
  103.         BEGIN  
  104.          DBMS_OUTPUT.PUT_LINE('提示信息:'||V_MESSAGE);   
  105.         END SHOW_MESSAGE;   
  106. ------------------------ 判断雇员是否存在函数 -------------------  
  107.          FUNCTION EXIST_EMP(P_EMPNO NUMBER)   
  108.          RETURN BOOLEAN    
  109.          IS  
  110.         V_NUM NUMBER; --局部变量  
  111.         BEGIN  
  112.         SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;   
  113. IF V_NUM=1 THEN    
  114.            RETURN TRUE;   
  115.          ELSE  
  116.          RETURN FALSE;   
  117.         END IF;    
  118.         END EXIST_EMP;   
  119.         -----------------------------  
  120.         END EMP_PK;  
  1. CREATE OR REPLACE PACKAGE EMP_PK   
  2.         --包头部分   
  3.         IS  
  4.         V_EMP_COUNT NUMBER(5);                
  5.         --雇员人数  
  6.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER);  --初始化  
  7.         PROCEDURE LIST_EMP;                       
  8.         --显示雇员列表  
  9. PROCEDURE INSERT_EMP(P_EMPNO        NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,  
  10.         P_SAL NUMBER);                        
  11.         --插入雇员  
  12.         PROCEDURE DELETE_EMP(P_EMPNO NUMBER);       --删除雇员  
  13.          PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);   
  14.         --修改雇员工资  
  15.         END EMP_PK;  
  16.         /CREATE OR REPLACE PACKAGE BODY EMP_PK  
  17.          --包体部分   
  18.         IS  
  19.         V_MESSAGE VARCHAR2(50); --显示信息  
  20. V_MAX_SAL NUMBER(7); --工资上限  
  21.         V_MIN_SAL NUMBER(7); --工资下限  
  22.         FUNCTION EXIST_EMP(P_EMPNO NUMBER)  RETURN  BOOLEAN; --判断雇员是否存在函数  
  23.         PROCEDURE SHOW_MESSAGE; --显示信息过程  
  24.         ------------------------------- 初始化过程 ----------------------------  
  25.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)   
  26.         IS   
  27.         BEGIN  
  28.          SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;  
  29. V_MAX_SAL:=P_MAX;  
  30.          V_MIN_SAL:=P_MIN;  
  31.          V_MESSAGE:='初始化过程已经完成!';  
  32.          SHOW_MESSAGE;   
  33.         END INIT;  
  34. ---------------------------- 显示雇员列表过程 ---------------------  
  35.         PROCEDURE LIST_EMP   
  36.          IS   
  37.         BEGIN  
  38. DBMS_OUTPUT.PUT_LINE('姓名       职务      工资');  
  39.         FOR emp_rec IN (SELECT * FROM EMP)  
  40.         LOOP  
  41.     DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,'')||RPAD(emp_rec.job,10,' ')||TO_CHAR(emp_rec.sal));  
  42.          END LOOP;  
  43.          DBMS_OUTPUT.PUT_LINE('雇员总人数'||V_EMP_COUNT);  
  44.         END LIST_EMP;  
  45. ----------------------------- 插入雇员过程 -----------------------------  
  46.         PROCEDUREINSERT_EMP(P_EMPNO     NUMBER,P_ENAMEVARCHAR2,P_JOB    VARCHAR2,P_SAL NUMBER)  
  47.          IS   
  48.         BEGIN  
  49.         IF NOT EXIST_EMP(P_EMPNO) THEN  
  50.         INSERT INTO EMP(EMPNO,ENAME,JOB,SAL)        VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);  
  51.         COMMIT;   
  52.         V_EMP_COUNT:=V_EMP_COUNT+1;  
  53.         V_MESSAGE:='雇员'||P_EMPNO||'已插入!';  
  54.         ELSE  
  55. V_MESSAGE:='雇员'||P_EMPNO||'已存在,不能插入!';  
  56.       END IF;  
  57.      SHOW_MESSAGE;   
  58.      EXCEPTION  
  59.     WHEN OTHERS THEN  
  60.      V_MESSAGE:='雇员'||P_EMPNO||'插入失败!';  
  61.      SHOW_MESSAGE;  
  62.      END INSERT_EMP;  
  63. --------------------------- 删除雇员过程 --------------------  
  64.          PROCEDURE DELETE_EMP(P_EMPNO NUMBER)   
  65.         IS   
  66.         BEGIN   
  67.         IF EXIST_EMP(P_EMPNO) THEN  
  68.         DELETE FROM EMP WHERE EMPNO=P_EMPNO;  
  69.         COMMIT;  
  70.          V_EMP_COUNT:=V_EMP_COUNT-1;  
  71.          V_MESSAGE:='雇员'||P_EMPNO||'已删除!';  
  72.          ELSE  
  73. V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能删除!';  
  74.     END IF;  
  75.     SHOW_MESSAGE;  
  76.      EXCEPTION  
  77.      WHEN OTHERS THEN  
  78.      V_MESSAGE:='雇员'||P_EMPNO||'删除失败!';  
  79.      SHOW_MESSAGE;  
  80.     END DELETE_EMP;  
  81. --------------------------------------- 修改雇员工资过程 ------------------------------------  
  82.         PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)   
  83.          IS   
  84.          BEGIN   
  85.          IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN  
  86.          V_MESSAGE:='工资超出修改范围!';  
  87.         ELSIF NOT EXIST_EMP(P_EMPNO) THEN  
  88.         V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能修改工资!';  
  89. ELSE  
  90.          UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;  
  91.         COMMIT;  
  92.         V_MESSAGE:='雇员'||P_EMPNO||'工资已经修改!';  
  93.         END IF;  
  94.         SHOW_MESSAGE;  
  95.         EXCEPTION  
  96.          WHEN OTHERS THEN  
  97.          V_MESSAGE:='雇员'||P_EMPNO||'工资修改失败!';  
  98.          SHOW_MESSAGE;  
  99.          END CHANGE_EMP_SAL;  
  100. ---------------------------- 显示信息过程 ----------------------------  
  101.          PROCEDURE SHOW_MESSAGE   
  102.         IS   
  103.         BEGIN  
  104.          DBMS_OUTPUT.PUT_LINE('提示信息:'||V_MESSAGE);  
  105.         END SHOW_MESSAGE;  
  106. ------------------------ 判断雇员是否存在函数 -------------------  
  107.          FUNCTION EXIST_EMP(P_EMPNO NUMBER)  
  108.          RETURN BOOLEAN   
  109.          IS  
  110.         V_NUM NUMBER; --局部变量  
  111.         BEGIN  
  112.         SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;  
  113. IF V_NUM=1 THEN   
  114.            RETURN TRUE;  
  115.          ELSE  
  116.          RETURN FALSE;  
  117.         END IF;   
  118.         END EXIST_EMP;  
  119.         -----------------------------  
  120.         END EMP_PK;  
结果为:
Sql代码 复制代码
  1. 程序包已创建。   
  2.         程序包主体已创建。  
  1. 程序包已创建。  
  2.         程序包主体已创建。  

步骤2:初始化包:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2. EXECUTE EMP_PK.INIT(6000,600);  
  1. SET SERVEROUTPUT ON  
  2. EXECUTE EMP_PK.INIT(6000,600);  

显示为:
Sql代码 复制代码
  1. 提示信息:初始化过程已经完成!  
  1. 提示信息:初始化过程已经完成!  

步骤3:显示雇员列表:
Sql代码 复制代码
  1. EXECUTE EMP_PK.LIST_EMP;  
  1. EXECUTE EMP_PK.LIST_EMP;  

显示为:
Sql代码 复制代码
  1. 姓名          职务          工资   
  2.         SMITH       CLERK       1560   
  3.         ALLEN       SALESMAN    1936   
  4.         WARD        SALESMAN    1830   
  5.         JONES       MANAGER     2975   
  6.         ...   
  7.         雇员总人数:14   
  8.         PL/SQL 过程已成功完成。  
  1. 姓名          职务          工资  
  2.         SMITH       CLERK       1560  
  3.         ALLEN       SALESMAN    1936  
  4.         WARD        SALESMAN    1830  
  5.         JONES       MANAGER     2975  
  6.         ...  
  7.         雇员总人数:14  
  8.         PL/SQL 过程已成功完成。  

步骤4:插入一个新记录:
Sql代码 复制代码
  1. EXECUTE EMP_PK.INSERT_EMP(8001,'小王','CLERK',1000);  
  1. EXECUTE EMP_PK.INSERT_EMP(8001,'小王','CLERK',1000);  

显示结果为:
Sql代码 复制代码
  1. 提示信息:雇员8001已插入!   
  2. PL/SQL 过程已成功完成。  
  1. 提示信息:雇员8001已插入!  
  2. PL/SQL 过程已成功完成。  

步骤5:通过全局变量V_EMP_COUNT查看雇员人数:
Sql代码 复制代码
  1. BEGIN  
  2. DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);   
  3. END;  
  1. BEGIN  
  2. DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);  
  3. END;  

显示结果为:
Sql代码 复制代码
  1. 15   
  2. PL/SQL 过程已成功完成。  
  1. 15  
  2. PL/SQL 过程已成功完成。  

步骤6:删除新插入记录:
Sql代码 复制代码
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  

显示结果为:
Sql代码 复制代码
  1. 提示信息:雇员8001已删除!   
  2.         PL/SQL 过程已成功完成。  
  1. 提示信息:雇员8001已删除!  
  2.         PL/SQL 过程已成功完成。  

再次删除该雇员:
Sql代码 复制代码
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  

结果为:
Sql代码 复制代码
  1. 提示信息:雇员8001不存在,不能删除!  
  1. 提示信息:雇员8001不存在,不能删除!  

步骤7:修改雇员工资:
Sql代码 复制代码
  1. EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);  
  1. EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);  

显示结果为:
Sql代码 复制代码
  1. 提示信息:工资超出修改范围!   
  2.         PL/SQL 过程已成功完成。  
  1. 提示信息:工资超出修改范围!  
  2.         PL/SQL 过程已成功完成。  

步骤8:授权其他用户调用包:
如果是另外一个用户要使用该包,必须由包的所有者授权,下面授予STUDEN账户对该包的使用权:
Sql代码 复制代码
  1. GRANT EXECUTE ON EMP_PK TO STUDENT;  
  1. GRANT EXECUTE ON EMP_PK TO STUDENT;  

每一个新的会话要为包中的公用变量开辟新的存储空间,所以需要重新执行初始化过程。两个会话的进程互不影响。
步骤9:其他用户调用包。
启动另外一个SQL*Plus,登录STUDENT账户,执行以下过程:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);  
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);  

结果为:
Sql代码 复制代码
  1. 提示信息:初始化过程已经完成!   
  2.         PL/SQL 过程已成功完成。  
  1. 提示信息:初始化过程已经完成!  
  2.         PL/SQL 过程已成功完成。  

说明:在初始化中设置雇员的总人数和修改工资的上、下限,初始化后V_EMP_COUNT为14人,插入雇员后V_EMP_COUNT为15人。V_EMP_COUNT为公有变量,所以可以在外部程序中使用DBMS_OUTPUT.PUT_LINE输出,引用时用EMP_PK.V_EMP_COUNT的形式,说明所属的包。而私有变量V_MAX_SAL和V_MIN_SAL不能被外部访问,只能通过内部过程来修改。同样,EXIST_EMP和SHOW_MESSAGE也是私有过程,也只能在过程体内被其他模块引用。
注意:在最后一个步骤中,因为STUDENT模式调用了SCOTT模式的包,所以包名前要增加模式名SCOTT。不同的会话对包的调用属于不同的应用,所以需要重新进行初始化。
练习
1.如果存储过程的参数类型为OUT,那么调用时传递的参数应该为:
     A.常量 B.表达式                C.变量 D.都可以
2.下列有关存储过程的特点说法错误的是:
     A.存储过程不能将值传回调用的主程序
     B.存储过程是一个命名的模块
     C.编译的存储过程存放在数据库中
     D.一个存储过程可以调用另一个存储过程
3.下列有关函数的特点说法错误的是:
     A.函数必须定义返回类型
     B.函数参数的类型只能是IN
     C.在函数体内可以多次使用RETURN语句
     D.函数的调用应使用EXECUTE命令
4.包中不能包含的元素为:
     A.存储过程 B.存储函数
     C.游标    D.表
5.下列有关包的使用说法错误的是:
     A.在不同的包内模块可以重名
     B.包的私有过程不能被外部程序调用
     C.包体中的过程和函数必须在包头部分说明
     D.必须先创建包头,然后创建包体

oracle存储过程实例

分类: 数据(仓)库及处理 1055人阅读 评论(2)收藏 举报
认识存储过程和函数
存储过程和函数也是一种PL/SQL块,是存入数据库的PL/SQL块。但存储过程和函数不同于已经介绍过的PL/SQL程序,我们通常把PL/SQL程序称为无名块,而存储过程和函数是以命名的方式存储于数据库中的。和PL/SQL程序相比,存储过程有很多优点,具体归纳如下:
* 存储过程和函数以命名的数据库对象形式存储于数据库当中。存储在数据库中的优点是很明显的,因为代码不保存在本地,用户可以在任何客户机上登录到数据库,并调用或修改代码。
* 存储过程和函数可由数据库提供安全保证,要想使用存储过程和函数,需要有存储过程和函数的所有者的授权,只有被授权的用户或创建者本身才能执行存储过程或调用函数。
* 存储过程和函数的信息是写入数据字典的,所以存储过程可以看作是一个公用模块,用户编写的PL/SQL程序或其他存储过程都可以调用它(但存储过程和函数不能调用PL/SQL程序)。一个重复使用的功能,可以设计成为存储过程,比如:显示一张工资统计表,可以设计成为存储过程;一个经常调用的计算,可以设计成为存储函数;根据雇员编号返回雇员的姓名,可以设计成存储函数。
* 像其他高级语言的过程和函数一样,可以传递参数给存储过程或函数,参数的传递也有多种方式。存储过程可以有返回值,也可以没有返回值,存储过程的返回值必须通过参数带回;函数有一定的数据类型,像其他的标准函数一样,我们可以通过对函数名的调用返回函数值。
   存储过程和函数需要进行编译,以排除语法错误,只有编译通过才能调用。
创建和删除存储过程
创建存储过程,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系统权限。该权限可由系统管理员授予。创建一个存储过程的基本语句如下:
CREATE [OR REPLACE] PROCEDURE 存储过程名[(参数[IN|OUT|IN OUT] 数据类型...)]
{AS|IS}
[说明部分]
BEGIN
可执行部分
[EXCEPTION
错误处理部分]
END [过程名];
其中:
可选关键字OR REPLACE 表示如果存储过程已经存在,则用新的存储过程覆盖,通常用于存储过程的重建。
参数部分用于定义多个参数(如果没有参数,就可以省略)。参数有三种形式:IN、OUT和IN OUT。如果没有指明参数的形式,则默认为IN。
关键字AS也可以写成IS,后跟过程的说明部分,可以在此定义过程的局部变量。
编写存储过程可以使用任何文本编辑器或直接在SQL*Plus环境下进行,编写好的存储过程必须要在SQL*Plus环境下进行编译,生成编译代码,原代码和编译代码在编译过程中都会被存入数据库。编译成功的存储过程就可以在Oracle环境下进行调用了。
一个存储过程在不需要时可以删除。删除存储过程的人是过程的创建者或者拥有DROP ANY PROCEDURE系统权限的人。删除存储过程的语法如下:
DROP PROCEDURE 存储过程名;
如果要重新编译一个存储过程,则只能是过程的创建者或者拥有ALTER ANY PROCEDURE系统权限的人。语法如下:
ALTER PROCEDURE 存储过程名 COMPILE;
执行(或调用)存储过程的人是过程的创建者或是拥有EXECUTE ANY PROCEDURE系统权限的人或是被拥有者授予EXECUTE权限的人。执行的方法如下:
方法1:
EXECUTE 模式名.存储过程名[(参数...)];
方法2:
BEGIN
模式名.存储过程名[(参数...)];
END;
传递的参数必须与定义的参数类型、个数和顺序一致(如果参数定义了默认值,则调用时可以省略参数)。参数可以是变量、常量或表达式,用法参见下一节。
如果是调用本账户下的存储过程,则模式名可以省略。要调用其他账户编写的存储过程,则模式名必须要添加。
以下是一个生成和调用简单存储过程的训练。注意要事先授予创建存储过程的权限。
【训练1】  创建一个显示雇员总人数的存储过程。
步骤1:登录SCOTT账户(或学生个人账户)。
步骤2:在SQL*Plus输入区中,输入以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT   
  2. AS  
  3. V_TOTAL NUMBER(10);   
  4. BEGIN  
  5.  SELECT COUNT(*) INTO V_TOTAL FROM EMP;   
  6.  DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_TOTAL);   
  7. END;  
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT  
  2. AS  
  3. V_TOTAL NUMBER(10);  
  4. BEGIN  
  5.  SELECT COUNT(*) INTO V_TOTAL FROM EMP;  
  6.  DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_TOTAL);  
  7. END;  

步骤3:按“执行”按钮进行编译。
如果存在错误,就会显示:
警告: 创建的过程带有编译错误。
如果存在错误,对脚本进行修改,直到没有错误产生。
如果编译结果正确,将显示:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤4:调用存储过程,在输入区中输入以下语句并执行:
Sql代码 复制代码
  1. EXECUTE EMP_COUNT;  
  1. EXECUTE EMP_COUNT;  

显示结果为:
Sql代码 复制代码
  1. 雇员总人数为:14   
  2.         PL/SQL 过程已成功完成。  
  1. 雇员总人数为:14  
  2.         PL/SQL 过程已成功完成。  

说明:在该训练中,V_TOTAL变量是存储过程定义的局部变量,用于接收查询到的雇员总人数。
注意:在SQL*Plus中输入存储过程,按“执行”按钮是进行编译,不是执行存储过程。
  如果在存储过程中引用了其他用户的对象,比如表,则必须有其他用户授予的对象访问权限。一个存储过程一旦编译成功,就可以由其他用户或程序来引用。但存储过程或函数的所有者必须授予其他用户执行该过程的权限。
存储过程没有参数,在调用时,直接写过程名即可。
【训练2】  在PL/SQL程序中调用存储过程。
步骤1:登录SCOTT账户。
步骤2:授权STUDENT账户使用该存储过程,即在SQL*Plus输入区中,输入以下的命令:
Sql代码 复制代码
  1. GRANT EXECUTE ON EMP_COUNT TO STUDENT  
  1. GRANT EXECUTE ON EMP_COUNT TO STUDENT  

Sql代码 复制代码
  1. 授权成功。  
  1. 授权成功。  

步骤3:登录STUDENT账户,在SQL*Plus输入区中输入以下程序:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2.         BEGIN  
  3.         SCOTT.EMP_COUNT;   
  4.         END;  
  1. SET SERVEROUTPUT ON  
  2.         BEGIN  
  3.         SCOTT.EMP_COUNT;  
  4.         END;  

步骤4:执行以上程序,结果为:
Sql代码 复制代码
  1. 雇员总人数为:14   
  2.         PL/SQL 过程已成功完成。   
  1. 雇员总人数为:14  
  2.         PL/SQL 过程已成功完成。   

  说明:在本例中,存储过程是由SCOTT账户创建的,STUDEN账户获得SCOTT账户的授权后,才能调用该存储过程。
  注意:在程序中调用存储过程,使用了第二种语法。
【训练3】  编写显示雇员信息的存储过程EMP_LIST,并引用EMP_COUNT存储过程。
步骤1:在SQL*Plus输入区中输入并编译以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE EMP_LIST   
  2.         AS  
  3.          CURSOR emp_cursor IS    
  4.         SELECT empno,ename FROM emp;   
  5.         BEGIN  
  6. FOR Emp_record IN emp_cursor LOOP      
  7.     DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);   
  8.         END LOOP;   
  9.         EMP_COUNT;   
  10.         END;  
  1. CREATE OR REPLACE PROCEDURE EMP_LIST  
  2.         AS  
  3.          CURSOR emp_cursor IS   
  4.         SELECT empno,ename FROM emp;  
  5.         BEGIN  
  6. FOR Emp_record IN emp_cursor LOOP     
  7.     DBMS_OUTPUT.PUT_LINE(Emp_record.empno||Emp_record.ename);  
  8.         END LOOP;  
  9.         EMP_COUNT;  
  10.         END;  

执行结果:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤2:调用存储过程,在输入区中输入以下语句并执行:
Sql代码 复制代码
  1. EXECUTE EMP_LIST  
  1. EXECUTE EMP_LIST  

显示结果为:
Sql代码 复制代码
  1. 7369SMITH   
  2. 7499ALLEN   
  3. 7521WARD   
  4. 7566JONES   
  5.             执行结果:   
  6.         雇员总人数为:14   
  7.         PL/SQL 过程已成功完成。  
  1. 7369SMITH  
  2. 7499ALLEN  
  3. 7521WARD  
  4. 7566JONES  
  5.             执行结果:  
  6.         雇员总人数为:14  
  7.         PL/SQL 过程已成功完成。  

说明:以上的EMP_LIST存储过程中定义并使用了游标,用来循环显示所有雇员的信息。然后调用已经成功编译的存储过程EMP_COUNT,用来附加显示雇员总人数。通过EXECUTE命令来执行EMP_LIST存储过程。
【练习1】编写显示部门信息的存储过程DEPT_LIST,要求统计出部门个数。
参数传递
参数的作用是向存储过程传递数据,或从存储过程获得返回结果。正确的使用参数可以大大增加存储过程的灵活性和通用性。
参数的类型有三种,如下所示。
Sql代码 复制代码
  1. IN  定义一个输入参数变量,用于传递参数给存储过程   
  2. OUT 定义一个输出参数变量,用于从存储过程获取数据   
  3. IN OUT  定义一个输入、输出参数变量,兼有以上两者的功能  
  1. IN  定义一个输入参数变量,用于传递参数给存储过程  
  2. OUT 定义一个输出参数变量,用于从存储过程获取数据  
  3. IN OUT  定义一个输入、输出参数变量,兼有以上两者的功能  

参数的定义形式和作用如下:
参数名 IN 数据类型 DEFAULT 值;
定义一个输入参数变量,用于传递参数给存储过程。在调用存储过程时,主程序的实际参数可以是常量、有值变量或表达式等。DEFAULT 关键字为可选项,用来设定参数的默认值。如果在调用存储过程时不指明参数,则参数变量取默认值。在存储过程中,输入变量接收主程序传递的值,但不能对其进行赋值。
参数名 OUT 数据类型;
定义一个输出参数变量,用于从存储过程获取数据,即变量从存储过程中返回值给主程序。
在调用存储过程时,主程序的实际参数只能是一个变量,而不能是常量或表达式。在存储过程中,参数变量只能被赋值而不能将其用于赋值,在存储过程中必须给输出变量至少赋值一次。
参数名 IN OUT 数据类型 DEFAULT 值;
定义一个输入、输出参数变量,兼有以上两者的功能。在调用存储过程时,主程序的实际参数只能是一个变量,而不能是常量或表达式。DEFAULT 关键字为可选项,用来设定参数的默认值。在存储过程中,变量接收主程序传递的值,同时可以参加赋值运算,也可以对其进行赋值。在存储过程中必须给变量至少赋值一次。
如果省略IN、OUT或IN OUT,则默认模式是IN。
【训练1】  编写给雇员增加工资的存储过程CHANGE_SALARY,通过IN类型的参数传递要增加工资的雇员编号和增加的工资额。
步骤1:登录SCOTT账户。
  步骤2:在SQL*Plus输入区中输入以下存储过程并执行:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)   
  2.         AS  
  3.          V_ENAME VARCHAR2(10);   
  4. V_SAL NUMBER(5);   
  5.         BEGIN  
  6.         SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;   
  7.          UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;   
  8.          DBMS_OUTPUT.PUT_LINE('雇员'||V_ENAME||'的工资被改为'||TO_CHAR(V_SAL+P_RAISE));   
  9. COMMIT;   
  10.         EXCEPTION   
  11.          WHEN OTHERS THEN  
  12.         DBMS_OUTPUT.PUT_LINE('发生错误,修改失败!');   
  13.         ROLLBACK;   
  14.         END;  
  1. CREATE OR REPLACE PROCEDURE CHANGE_SALARY(P_EMPNO IN NUMBER DEFAULT 7788,P_RAISE NUMBER DEFAULT 10)  
  2.         AS  
  3.          V_ENAME VARCHAR2(10);  
  4. V_SAL NUMBER(5);  
  5.         BEGIN  
  6.         SELECT ENAME,SAL INTO V_ENAME,V_SAL FROM EMP WHERE EMPNO=P_EMPNO;  
  7.          UPDATE EMP SET SAL=SAL+P_RAISE WHERE EMPNO=P_EMPNO;  
  8.          DBMS_OUTPUT.PUT_LINE('雇员'||V_ENAME||'的工资被改为'||TO_CHAR(V_SAL+P_RAISE));  
  9. COMMIT;  
  10.         EXCEPTION  
  11.          WHEN OTHERS THEN  
  12.         DBMS_OUTPUT.PUT_LINE('发生错误,修改失败!');  
  13.         ROLLBACK;  
  14.         END;  

执行结果为:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤3:调用存储过程,在输入区中输入以下语句并执行:
Sql代码 复制代码
  1. EXECUTE CHANGE_SALARY(7788,80)  
  1. EXECUTE CHANGE_SALARY(7788,80)  

显示结果为:
Sql代码 复制代码
  1. 雇员SCOTT的工资被改为3080   
  1. 雇员SCOTT的工资被改为3080   

说明:从执行结果可以看到,雇员SCOTT的工资已由原来的3000改为3080。
参数的值由调用者传递,传递的参数的个数、类型和顺序应该和定义的一致。如果顺序不一致,可以采用以下调用方法。如上例,执行语句可以改为:
 EXECUTE CHANGE_SALARY(P_RAISE=>80,P_EMPNO=>7788);
  可以看出传递参数的顺序发生了变化,并且明确指出了参数名和要传递的值,=>运算符左侧是参数名,右侧是参数表达式,这种赋值方法的意义较清楚。
【练习1】创建插入雇员的存储过程INSERT_EMP,并将雇员编号等作为参数。
在设计存储过程的时候,也可以为参数设定默认值,这样调用者就可以不传递或少传递参数了。
【训练2】  调用存储过程CHANGE_SALARY,不传递参数,使用默认参数值。
在SQL*Plus输入区中输入以下命令并执行:
Sql代码 复制代码
  1. EXECUTE CHANGE_SALARY  
  1. EXECUTE CHANGE_SALARY  

显示结果为:
Sql代码 复制代码
  1. 雇员SCOTT的工资被改为3090   
  1. 雇员SCOTT的工资被改为3090   

说明:在存储过程的调用中没有传递参数,而是采用了默认值7788和10,即默认雇员号为7788,增加的工资为10。
【训练3】  使用OUT类型的参数返回存储过程的结果。
步骤1:登录SCOTT账户。
步骤2:在SQL*Plus输入区中输入并编译以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)   
  2.         AS  
  3.         BEGIN  
  4.         SELECT COUNT(*) INTO P_TOTAL FROM EMP;   
  5.         END;  
  1. CREATE OR REPLACE PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)  
  2.         AS  
  3.         BEGIN  
  4.         SELECT COUNT(*) INTO P_TOTAL FROM EMP;  
  5.         END;  

执行结果为:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤3:输入以下程序并执行:
Sql代码 复制代码
  1. DECLARE  
  2.         V_EMPCOUNT NUMBER;   
  3.         BEGIN  
  4.         EMP_COUNT(V_EMPCOUNT);   
  5.         DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_EMPCOUNT);   
  6.         END;  
  1. DECLARE  
  2.         V_EMPCOUNT NUMBER;  
  3.         BEGIN  
  4.         EMP_COUNT(V_EMPCOUNT);  
  5.         DBMS_OUTPUT.PUT_LINE('雇员总人数为:'||V_EMPCOUNT);  
  6.         END;  

显示结果为:
Sql代码 复制代码
  1. 雇员总人数为:14   
  2.         PL/SQL 过程已成功完成。  
  1. 雇员总人数为:14  
  2.         PL/SQL 过程已成功完成。  

    说明:在存储过程中定义了OUT类型的参数P_TOTAL,在主程序调用该存储过程时,传递了参数V_EMPCOUNT。在存储过程中的SELECT...INTO...语句中对P_TOTAL进行赋值,赋值结果由V_EMPCOUNT变量带回给主程序并显示。
以上程序要覆盖同名的EMP_COUNT存储过程,如果不使用OR REPLACE选项,就会出现以下错误:
Sql代码 复制代码
  1. ERROR 位于第 1 行:   
  2.         ORA-00955: 名称已由现有对象使用。  
  1. ERROR 位于第 1 行:  
  2.         ORA-00955: 名称已由现有对象使用。  

【练习2】创建存储过程,使用OUT类型参数获得雇员经理名。
【训练4】  使用IN OUT类型的参数,给电话号码增加区码。
步骤1:登录SCOTT账户。
步骤2:在SQL*Plus输入区中输入并编译以下存储过程:
Sql代码 复制代码
  1. CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)   
  2.         AS  
  3.         BEGIN  
  4.          P_HPONE_NUM:='0755-'||P_HPONE_NUM;   
  5.         END;  
  1. CREATE OR REPLACE PROCEDURE ADD_REGION(P_HPONE_NUM IN OUT VARCHAR2)  
  2.         AS  
  3.         BEGIN  
  4.          P_HPONE_NUM:='0755-'||P_HPONE_NUM;  
  5.         END;  

执行结果为:
Sql代码 复制代码
  1. 过程已创建。  
  1. 过程已创建。  

步骤3:输入以下程序并执行:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3. V_PHONE_NUM VARCHAR2(15);   
  4. BEGIN  
  5. V_PHONE_NUM:='26731092';   
  6. ADD_REGION(V_PHONE_NUM);   
  7. DBMS_OUTPUT.PUT_LINE('新的电话号码:'||V_PHONE_NUM);   
  8. END;  
  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3. V_PHONE_NUM VARCHAR2(15);  
  4. BEGIN  
  5. V_PHONE_NUM:='26731092';  
  6. ADD_REGION(V_PHONE_NUM);  
  7. DBMS_OUTPUT.PUT_LINE('新的电话号码:'||V_PHONE_NUM);  
  8. END;  

显示结果为:
Sql代码 复制代码
  1. 新的电话号码:0755-26731092   
  2.         PL/SQL 过程已成功完成。  
  1. 新的电话号码:0755-26731092  
  2.         PL/SQL 过程已成功完成。  

说明:变量V_HPONE_NUM既用来向存储过程传递旧电话号码,也用来向主程序返回新号码。新的号码在原来基础上增加了区号0755和-。
创建和删除存储函数
  创建函数,需要有CREATE PROCEDURE或CREATE ANY PROCEDURE的系统权限。该权限可由系统管理员授予。创建存储函数的语法和创建存储过程的类似,即
CREATE [OR REPLACE] FUNCTION 函数名[(参数[IN] 数据类型...)]
RETURN 数据类型
{AS|IS}
[说明部分]
BEGIN
可执行部分
RETURN (表达式)
[EXCEPTION
    错误处理部分]
END [函数名];
其中,参数是可选的,但只能是IN类型(IN关键字可以省略)。
在定义部分的RETURN 数据类型,用来表示函数的数据类型,也就是返回值的类型,此部分不可省略。
在可执行部分的RETURN(表达式),用来生成函数的返回值,其表达式的类型应该和定义部分说明的函数返回值的数据类型一致。在函数的执行部分可以有多个RETURN语句,但只有一个RETURN语句会被执行,一旦执行了RETURN语句,则函数结束并返回调用环境。
一个存储函数在不需要时可以删除,但删除的人应是函数的创建者或者是拥有DROP ANY PROCEDURE系统权限的人。其语法如下:
DROP FUNCTION 函数名;
重新编译一个存储函数时,编译的人应是函数的创建者或者拥有ALTER ANY PROCEDURE系统权限的人。重新编译一个存储函数的语法如下:
ALTER PROCEDURE 函数名 COMPILE;
函数的调用者应是函数的创建者或拥有EXECUTE ANY PROCEDURE系统权限的人,或是被函数的拥有者授予了函数执行权限的账户。函数的引用和存储过程不同,函数要出现在程序体中,可以参加表达式的运算或单独出现在表达式中,其形式如下:
变量名:=函数名(...)
【训练1】  创建一个通过雇员编号返回雇员名称的函数GET_EMP_NAME。
步骤1:登录SCOTT账户。
步骤2:在SQL*Plus输入区中输入以下存储函数并编译:
Sql代码 复制代码
  1. CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)   
  2.         RETURN VARCHAR2   
  3.         AS  
  4.          V_ENAME VARCHAR2(10);   
  5.         BEGIN  
  6.         ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;   
  7. RETURN(V_ENAME);   
  8. EXCEPTION   
  9.  WHEN NO_DATA_FOUND THEN  
  10.   DBMS_OUTPUT.PUT_LINE('没有该编号雇员!');   
  11.   RETURN (NULL);   
  12.  WHEN TOO_MANY_ROWS THEN  
  13.   DBMS_OUTPUT.PUT_LINE('有重复雇员编号!');   
  14.   RETURN (NULL);   
  15.  WHEN OTHERS THEN  
  16.   DBMS_OUTPUT.PUT_LINE('发生其他错误!');   
  17.   RETURN (NULL);   
  18. END;  
  1. CREATE OR REPLACE FUNCTION GET_EMP_NAME(P_EMPNO NUMBER DEFAULT 7788)  
  2.         RETURN VARCHAR2  
  3.         AS  
  4.          V_ENAME VARCHAR2(10);  
  5.         BEGIN  
  6.         ELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO=P_EMPNO;  
  7. RETURN(V_ENAME);  
  8. EXCEPTION  
  9.  WHEN NO_DATA_FOUND THEN  
  10.   DBMS_OUTPUT.PUT_LINE('没有该编号雇员!');  
  11.   RETURN (NULL);  
  12.  WHEN TOO_MANY_ROWS THEN  
  13.   DBMS_OUTPUT.PUT_LINE('有重复雇员编号!');  
  14.   RETURN (NULL);  
  15.  WHEN OTHERS THEN  
  16.   DBMS_OUTPUT.PUT_LINE('发生其他错误!');  
  17.   RETURN (NULL);  
  18. END;  

步骤3:调用该存储函数,输入并执行以下程序:
Sql代码 复制代码
  1. BEGIN  
  2.         DBMS_OUTPUT.PUT_LINE('雇员7369的名称是:'|| GET_EMP_NAME(7369));   
  3.          DBMS_OUTPUT.PUT_LINE('雇员7839的名称是:'|| GET_EMP_NAME(7839));   
  4.         END;  
  1. BEGIN  
  2.         DBMS_OUTPUT.PUT_LINE('雇员7369的名称是:'|| GET_EMP_NAME(7369));  
  3.          DBMS_OUTPUT.PUT_LINE('雇员7839的名称是:'|| GET_EMP_NAME(7839));  
  4.         END;  

显示结果为:
Sql代码 复制代码
  1. 雇员7369的名称是:SMITH   
  2.         雇员7839的名称是:KING   
  3.         PL/SQL 过程已成功完成。  
  1. 雇员7369的名称是:SMITH  
  2.         雇员7839的名称是:KING  
  3.         PL/SQL 过程已成功完成。  

说明:函数的调用直接出现在程序的DBMS_OUTPUT.PUT_LINE语句中,作为字符串表达式的一部分。如果输入了错误的雇员编号,就会在函数的错误处理部分输出错误信息。试修改雇员编号,重新运行调用部分。
【练习1】创建一个通过部门编号返回部门名称的存储函数GET_DEPT_NAME。
   【练习2】将函数的执行权限授予STUDENT账户,然后登录STUDENT账户调用。
存储过程和函数的查看
可以通过对数据字典的访问来查询存储过程或函数的有关信息,如果要查询当前用户的存储过程或函数的源代码,可以通过对USER_SOURCE数据字典视图的查询得到。USER_SOURCE的结构如下:
Sql代码 复制代码
  1. DESCRIBE USER_SOURCE  
  1. DESCRIBE USER_SOURCE  

结果为:
Sql代码 复制代码
  1. 名称                                      是否为空? 类型   
  2.         ------------------------------------------------------------- ------------- -----------------------  
  3.  NAME                                               VARCHAR2(30)   
  4.  TYPE                                               VARCHAR2(12)   
  5.  LINE                                               NUMBER   
  6.  TEXT                                               VARCHAR2(4000)  
  1. 名称                                      是否为空? 类型  
  2.         ------------------------------------------------------------- ------------- -----------------------  
  3.  NAME                                               VARCHAR2(30)  
  4.  TYPE                                               VARCHAR2(12)  
  5.  LINE                                               NUMBER  
  6.  TEXT                                               VARCHAR2(4000)  

  说明:里面按行存放着过程或函数的脚本,NAME是过程或函数名,TYPE 代表类型(PROCEDURE或FUNCTION),LINE是行号,TEXT 为脚本。
【训练1】  查询过程EMP_COUNT的脚本。
在SQL*Plus中输入并执行如下查询:
Sql代码 复制代码
  1. select TEXT  from user_source WHERE NAME='EMP_COUNT';  
  1. select TEXT  from user_source WHERE NAME='EMP_COUNT';  

结果为:
Sql代码 复制代码
  1. TEXT   
  2. --------------------------------------------------------------------------------  
  3. PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)   
  4. AS  
  5. BEGIN  
  6.  SELECT COUNT(*) INTO P_TOTAL FROM EMP;   
  7. END;  
  1. TEXT  
  2. --------------------------------------------------------------------------------  
  3. PROCEDURE EMP_COUNT(P_TOTAL OUT NUMBER)  
  4. AS  
  5. BEGIN  
  6.  SELECT COUNT(*) INTO P_TOTAL FROM EMP;  
  7. END;  

【训练2】  查询过程GET_EMP_NAME的参数。
在SQL*Plus中输入并执行如下查询:
Sql代码 复制代码
  1. DESCRIBE GET_EMP_NAME  
  1. DESCRIBE GET_EMP_NAME  

结果为:
Sql代码 复制代码
  1. FUNCTION GET_EMP_NAME RETURNS VARCHAR2   
  2.         参数名称            类型          输入/输出默认值?   
  3.         ----------------------------------------- ----------------------------------- ----------------- -------------  
  4.         P_EMPNO             NUMBER(4) IN     DEFAULT  
  1. FUNCTION GET_EMP_NAME RETURNS VARCHAR2  
  2.         参数名称            类型          输入/输出默认值?  
  3.         ----------------------------------------- ----------------------------------- ----------------- -------------  
  4.         P_EMPNO             NUMBER(4) IN     DEFAULT  

【训练3】  在发生编译错误时,显示错误。
Sql代码 复制代码
  1. SHOW ERRORS  
  1. SHOW ERRORS  

以下是一段编译错误显示:
Sql代码 复制代码
  1. LINE/COL ERROR   
  2.         ------------- -----------------------------------------------------------------  
  3.         4/2       PL/SQL: SQL Statement ignored   
  4.         4/36      PLS-00201: 必须说明标识符 'EMPP'  
  1. LINE/COL ERROR  
  2.         ------------- -----------------------------------------------------------------  
  3.         4/2       PL/SQL: SQL Statement ignored  
  4.         4/36      PLS-00201: 必须说明标识符 'EMPP'  

  说明:查询一个存储过程或函数是否是有效状态(即编译成功),可以使用数据字典USER_OBJECTS的STATUS列。
【训练4】  查询EMP_LIST存储过程是否可用:
Sql代码 复制代码
  1. SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME='EMP_LIST';  
  1. SELECT STATUS FROM USER_OBJECTS WHERE OBJECT_NAME='EMP_LIST';  

结果为:
Sql代码 复制代码
  1. STATUS   
  2.         ------------  
  3.         VALID  
  1. STATUS  
  2.         ------------  
  3.         VALID  

说明:VALID表示该存储过程有效(即通过编译),INVALID表示存储过程无效或需要重新编译。当Oracle调用一个无效的存储过程或函数时,首先试图对其进行编译,如果编译成功则将状态置成VALID并执行,否则给出错误信息。
当一个存储过程编译成功,状态变为VALID,会不会在某些情况下变成INVALID。结论是完全可能的。比如一个存储过程中包含对表的查询,如果表被修改或删除,存储过程就会变成无效INVALID。所以要注意存储过程和函数对其他对象的依赖关系。
如果要检查存储过程或函数的依赖性,可以通过查询数据字典USER_DENPENDENCIES来确定,该表结构如下:
Sql代码 复制代码
  1. DESCRIBE USER_DEPENDENCIES;  
  1. DESCRIBE USER_DEPENDENCIES;  

结果:
Sql代码 复制代码
  1. 名称                     是否为空? 类型   
  2.         -------------------------------------------------------------- ------------- ----------------------------  
  3.          NAME            NOT NULL   VARCHAR2(30)   
  4.          TYPE                       VARCHAR2(12)   
  5.         REFERENCED_OWNER                                VARCHAR2(30)   
  6.  REFERENCED_NAME                                VARCHAR2(64)   
  7.  REFERENCED_TYPE                                VARCHAR2(12)   
  8. REFERENCED_LINK_NAME                            VARCHAR2(128)   
  9.         SCHEMAID                                        NUMBER   
  10.          DEPENDENCY_TYPE                                VARCHAR2(4)  
  1. 名称                     是否为空? 类型  
  2.         -------------------------------------------------------------- ------------- ----------------------------  
  3.          NAME            NOT NULL   VARCHAR2(30)  
  4.          TYPE                       VARCHAR2(12)  
  5.         REFERENCED_OWNER                                VARCHAR2(30)  
  6.  REFERENCED_NAME                                VARCHAR2(64)  
  7.  REFERENCED_TYPE                                VARCHAR2(12)  
  8. REFERENCED_LINK_NAME                            VARCHAR2(128)  
  9.         SCHEMAID                                        NUMBER  
  10.          DEPENDENCY_TYPE                                VARCHAR2(4)  

  说明:NAME为实体名,TYPE为实体类型,REFERENCED_OWNER为涉及到的实体拥有者账户,REFERENCED_NAME为涉及到的实体名,REFERENCED_TYPE 为涉及到的实体类型。
【训练5】  查询EMP_LIST存储过程的依赖性。
Sql代码 复制代码
  1. SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME='EMP_LIST';  
  1. SELECT REFERENCED_NAME,REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE NAME='EMP_LIST';  

执行结果:
Sql代码 复制代码
  1. REFERENCED_NAME                                         REFERENCED_TYPE   
  2.         ------------------------------------------------------------------------------------------ ----------------------------  
  3. STANDARD                                                PACKAGE   
  4.         SYS_STUB_FOR_PURITY_ANALYSIS                            PACKAGE   
  5.         DBMS_OUTPUT                                                 PACKAGE   
  6.         DBMS_OUTPUT                                             SYNONYM   
  7. DBMS_OUTPUT                      NON-EXISTENT   
  8.         EMP                                                         TABLE  
  9.         EMP_COUNT                                                   PROCEDURE  
  1. REFERENCED_NAME                                         REFERENCED_TYPE  
  2.         ------------------------------------------------------------------------------------------ ----------------------------  
  3. STANDARD                                                PACKAGE  
  4.         SYS_STUB_FOR_PURITY_ANALYSIS                            PACKAGE  
  5.         DBMS_OUTPUT                                                 PACKAGE  
  6.         DBMS_OUTPUT                                             SYNONYM  
  7. DBMS_OUTPUT                      NON-EXISTENT  
  8.         EMP                                                         TABLE  
  9.         EMP_COUNT                                                   PROCEDURE  

  说明:可以看出存储过程EMP_LIST依赖一些系统包、EMP表和EMP_COUNT存储过程。如果删除了EMP表或EMP_COUNT存储过程,EMP_LIST将变成无效。
还有一种情况需要我们注意:如果一个用户A被授予执行属于用户B的一个存储过程的权限,在用户B的存储过程中,访问到用户C的表,用户B被授予访问用户C的表的权限,但用户A没有被授予访问用户C表的权限,那么用户A调用用户B的存储过程是失败的还是成功的呢?答案是成功的。如果读者有兴趣,不妨进行一下实际测试。


包的概念和组成
包是用来存储相关程序结构的对象,它存储于数据字典中。包由两个分离的部分组成:包头(PACKAGE)和包体(PACKAGE BODY)。包头是包的说明部分,是对外的操作接口,对应用是可见的;包体是包的代码和实现部分,对应用来说是不可见的黑盒。
包中可以包含的程序结构如下所示。
Sql代码 复制代码
  1. 过程(PROCUDURE)   带参数的命名的程序模块   
  2. 函数(FUNCTION)    带参数、具有返回值的命名的程序模块   
  3. 变量(VARIABLE)    存储变化的量的存储单元   
  4. 常量(CONSTANT)    存储不变的量的存储单元   
  5. 游标(CURSOR)  用户定义的数据操作缓存区,在可执行部分使用   
  6. 类型(TYPE)    用户定义的新的结构类型   
  7. 异常(EXCEPTION)   在标准包中定义或由用户自定义,用于处理程序错误  
  1. 过程(PROCUDURE)   带参数的命名的程序模块  
  2. 函数(FUNCTION)    带参数、具有返回值的命名的程序模块  
  3. 变量(VARIABLE)    存储变化的量的存储单元  
  4. 常量(CONSTANT)    存储不变的量的存储单元  
  5. 游标(CURSOR)  用户定义的数据操作缓存区,在可执行部分使用  
  6. 类型(TYPE)    用户定义的新的结构类型  
  7. 异常(EXCEPTION)   在标准包中定义或由用户自定义,用于处理程序错误  

说明部分可以出现在包的三个不同的部分:出现在包头中的称为公有元素,出现在包体中的称为私有元素,出现在包体的过程(或函数)中的称为局部变量。它们的性质有所不同,如下所示。
Sql代码 复制代码
  1. 公有元素(PUBLIC)    在包头中说明,在包体中具体定义 在包外可见并可以访问,对整个应用的全过程有效   
  2. 私有元素(PRIVATE)   在包体的说明部分说明  只能被包内部的其他部分访问   
  3. 局部变量(LOCAL) 在过程或函数的说明部分说明   只能在定义变量的过程或函数中使用  
  1. 公有元素(PUBLIC)    在包头中说明,在包体中具体定义 在包外可见并可以访问,对整个应用的全过程有效  
  2. 私有元素(PRIVATE)   在包体的说明部分说明  只能被包内部的其他部分访问  
  3. 局部变量(LOCAL) 在过程或函数的说明部分说明   只能在定义变量的过程或函数中使用  

在包体中出现的过程或函数,如果需要对外公用,就必须在包头中说明,包头中的说明应该和包体中的说明一致。
包有以下优点:
* 包可以方便地将存储过程和函数组织到一起,每个包又是相互独立的。在不同的包中,过程、函数都可以重名,这解决了在同一个用户环境中命名的冲突问题。
* 包增强了对存储过程和函数的安全管理,对整个包的访问权只需一次授予。
  * 在同一个会话中,公用变量的值将被保留,直到会话结束。
* 区分了公有过程和私有过程,包体的私有过程增加了过程和函数的保密性。
* 包在被首次调用时,就作为一个整体被全部调入内存,减少了多次访问过程或函数的I/O次数。
创建包和包体
包由包头和包体两部分组成,包的创建应该先创建包头部分,然后创建包体部分。创建、删除和编译包的权限同创建、删除和编译存储过程的权限相同。
创建包头的简要语句如下:
CREATE [OR REPLACE] PACKAGE 包名
{IS|AS}
公有变量定义
公有类型定义
公有游标定义
公有异常定义
函数说明
过程说明
END;
创建包体的简要语法如下:
CREATE [OR REPLACE] PACKAGE BODY 包名
{IS|AS}
私有变量定义
私有类型定义
私有游标定义
私有异常定义
函数定义
过程定义
END;
包的其他操作命令包括:
删除包头:
DROP PACKAGE 包头名
删除包体:
DROP PACKAGE BODY 包体名
重新编译包头:
ALTER PACKAGE 包名 COMPILE PACKAGE
重新编译包体:
ALTER PACKAGE 包名 COMPILE PACKAGE BODY
在包头中说明的对象可以在包外调用,调用的方法和调用单独的过程或函数的方法基本相同,惟一的区别就是要在调用的过程或函数名前加上包的名字(中间用“.”分隔)。但要注意,不同的会话将单独对包的公用变量进行初始化,所以不同的会话对包的调用属于不同的应用。
系统包
Oracle预定义了很多标准的系统包,这些包可以在应用中直接使用,比如在训练中我们使用的DBMS_OUTPUT包,就是系统包。PUT_LINE是该包的一个函数。常用系统包下所示。
Sql代码 复制代码
  1. DBMS_OUTPUT 在SQL*Plus环境下输出信息   
  2. DBMS_DDL    编译过程函数和包   
  3. DBMS_SESSION    改变用户的会话,初始化包等   
  4. DBMS_TRANSACTION    控制数据库事务   
  5. DBMS_MAIL   连接Oracle*Mail   
  6. DBMS_LOCK   进行复杂的锁机制管理   
  7. DBMS_ALERT  识别数据库事件告警   
  8. DBMS_PIPE   通过管道在会话间传递信息   
  9. DBMS_JOB    管理Oracle的作业   
  10. DBMS_LOB    操纵大对象   
  11. DBMS_SQL    执行动态SQL语句  
  1. DBMS_OUTPUT 在SQL*Plus环境下输出信息  
  2. DBMS_DDL    编译过程函数和包  
  3. DBMS_SESSION    改变用户的会话,初始化包等  
  4. DBMS_TRANSACTION    控制数据库事务  
  5. DBMS_MAIL   连接Oracle*Mail  
  6. DBMS_LOCK   进行复杂的锁机制管理  
  7. DBMS_ALERT  识别数据库事件告警  
  8. DBMS_PIPE   通过管道在会话间传递信息  
  9. DBMS_JOB    管理Oracle的作业  
  10. DBMS_LOB    操纵大对象  
  11. DBMS_SQL    执行动态SQL语句  

包的应用
在SQL*Plus环境下,包和包体可以分别编译,也可以一起编译。如果分别编译,则要先编译包头,后编译包体。如果在一起编译,则包头写在前,包体在后,中间用“/”分隔。
可以将已经存在的存储过程或函数添加到包中,方法是去掉过程或函数创建语句的CREATE OR REPLACE部分,将存储过程或函数复制到包体中 ,然后重新编译即可。
   如果需要将私有过程或函数变成共有过程或函数的话,将过程或函数说明部分复制到包头说明部分,然后重新编译就可以了。
【训练1】  创建管理雇员信息的包EMPLOYE,它具有从EMP表获得雇员信息,修改雇员名称,修改雇员工资和写回EMP表的功能。
步骤1:登录SCOTT账户,输入以下代码并编译:
Sql代码 复制代码
  1. CREATE OR REPLACE PACKAGE EMPLOYE --包头部分   
  2.         IS  
  3.  PROCEDURE SHOW_DETAIL;    
  4.  PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);    
  5.  PROCEDURE SAVE_EMPLOYE;    
  6.  PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);    
  7. PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);    
  8.         END EMPLOYE;   
  9.         /   
  10.         CREATE OR REPLACE PACKAGE BODY EMPLOYE --包体部分   
  11.         IS  
  12.  EMPLOYE EMP%ROWTYPE;   
  13.         -------------- 显示雇员信息 ---------------  
  14.         PROCEDURE SHOW_DETAIL   
  15.         AS  
  16.         BEGIN  
  17. DBMS_OUTPUT.PUT_LINE(‘----- 雇员信息 -----’);     
  18.         DBMS_OUTPUT.PUT_LINE('雇员编号:'||EMPLOYE.EMPNO);   
  19.         DBMS_OUTPUT.PUT_LINE('雇员名称:'||EMPLOYE.ENAME);   
  20.           DBMS_OUTPUT.PUT_LINE('雇员职务:'||EMPLOYE.JOB);   
  21.          DBMS_OUTPUT.PUT_LINE('雇员工资:'||EMPLOYE.SAL);   
  22.          DBMS_OUTPUT.PUT_LINE('部门编号:'||EMPLOYE.DEPTNO);   
  23.         END SHOW_DETAIL;   
  24. ----------------- 从EMP表取得一个雇员 --------------------  
  25.          PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)   
  26.         AS  
  27.         BEGIN  
  28.         SELECT * INTO EMPLOYE FROM EMP WHERE    EMPNO=P_EMPNO;   
  29.         DBMS_OUTPUT.PUT_LINE('获取雇员'||EMPLOYE.ENAME||'信息成功');   
  30.          EXCEPTION   
  31.          WHEN OTHERS THEN  
  32.            DBMS_OUTPUT.PUT_LINE('获取雇员信息发生错误!');   
  33.         END GET_EMPLOYE;   
  34. ---------------------- 保存雇员到EMP表 --------------------------  
  35.         PROCEDURE SAVE_EMPLOYE   
  36.         AS  
  37.         BEGIN  
  38.         UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=   
  39.     EMPLOYE.EMPNO;   
  40.      DBMS_OUTPUT.PUT_LINE('雇员信息保存完成!');   
  41.         END SAVE_EMPLOYE;   
  42. ---------------------------- 修改雇员名称 ------------------------------  
  43.         PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)   
  44.          AS  
  45.         BEGIN  
  46.          EMPLOYE.ENAME:=P_NEWNAME;   
  47.          DBMS_OUTPUT.PUT_LINE('修改名称完成!');   
  48.         END CHANGE_NAME;   
  49. ---------------------------- 修改雇员工资 --------------------------  
  50.         PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)   
  51.         AS  
  52.         BEGIN  
  53.          EMPLOYE.SAL:=P_NEWSAL;   
  54.          DBMS_OUTPUT.PUT_LINE('修改工资完成!');   
  55.         END CHANGE_SAL;   
  56.         END EMPLOYE;  
  1. CREATE OR REPLACE PACKAGE EMPLOYE --包头部分   
  2.         IS  
  3.  PROCEDURE SHOW_DETAIL;   
  4.  PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER);   
  5.  PROCEDURE SAVE_EMPLOYE;   
  6.  PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2);   
  7. PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER);   
  8.         END EMPLOYE;  
  9.         /  
  10.         CREATE OR REPLACE PACKAGE BODY EMPLOYE --包体部分   
  11.         IS  
  12.  EMPLOYE EMP%ROWTYPE;  
  13.         -------------- 显示雇员信息 ---------------  
  14.         PROCEDURE SHOW_DETAIL  
  15.         AS  
  16.         BEGIN  
  17. DBMS_OUTPUT.PUT_LINE(‘----- 雇员信息 -----’);     
  18.         DBMS_OUTPUT.PUT_LINE('雇员编号:'||EMPLOYE.EMPNO);  
  19.         DBMS_OUTPUT.PUT_LINE('雇员名称:'||EMPLOYE.ENAME);  
  20.           DBMS_OUTPUT.PUT_LINE('雇员职务:'||EMPLOYE.JOB);  
  21.          DBMS_OUTPUT.PUT_LINE('雇员工资:'||EMPLOYE.SAL);  
  22.          DBMS_OUTPUT.PUT_LINE('部门编号:'||EMPLOYE.DEPTNO);  
  23.         END SHOW_DETAIL;  
  24. ----------------- 从EMP表取得一个雇员 --------------------  
  25.          PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER)  
  26.         AS  
  27.         BEGIN  
  28.         SELECT * INTO EMPLOYE FROM EMP WHERE    EMPNO=P_EMPNO;  
  29.         DBMS_OUTPUT.PUT_LINE('获取雇员'||EMPLOYE.ENAME||'信息成功');  
  30.          EXCEPTION  
  31.          WHEN OTHERS THEN  
  32.            DBMS_OUTPUT.PUT_LINE('获取雇员信息发生错误!');  
  33.         END GET_EMPLOYE;  
  34. ---------------------- 保存雇员到EMP表 --------------------------  
  35.         PROCEDURE SAVE_EMPLOYE  
  36.         AS  
  37.         BEGIN  
  38.         UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=  
  39.     EMPLOYE.EMPNO;  
  40.      DBMS_OUTPUT.PUT_LINE('雇员信息保存完成!');  
  41.         END SAVE_EMPLOYE;  
  42. ---------------------------- 修改雇员名称 ------------------------------  
  43.         PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2)  
  44.          AS  
  45.         BEGIN  
  46.          EMPLOYE.ENAME:=P_NEWNAME;  
  47.          DBMS_OUTPUT.PUT_LINE('修改名称完成!');  
  48.         END CHANGE_NAME;  
  49. ---------------------------- 修改雇员工资 --------------------------  
  50.         PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER)  
  51.         AS  
  52.         BEGIN  
  53.          EMPLOYE.SAL:=P_NEWSAL;  
  54.          DBMS_OUTPUT.PUT_LINE('修改工资完成!');  
  55.         END CHANGE_SAL;  
  56.         END EMPLOYE;  

步骤2:获取雇员7788的信息:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE EMPLOYE.GET_EMPLOYE(7788);  
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE EMPLOYE.GET_EMPLOYE(7788);  

结果为:
Sql代码 复制代码
  1. 获取雇员SCOTT信息成功   
  2.         PL/SQL 过程已成功完成。  
  1. 获取雇员SCOTT信息成功  
  2.         PL/SQL 过程已成功完成。  

步骤3:显示雇员信息:
Sql代码 复制代码
  1. EXECUTE EMPLOYE.SHOW_DETAIL;  
  1. EXECUTE EMPLOYE.SHOW_DETAIL;  

结果为:
Sql代码 复制代码
  1. ------------------ 雇员信息 ------------------  
  2.         雇员编号:7788   
  3.         雇员名称:SCOTT   
  4.         雇员职务:ANALYST   
  5.         雇员工资:3000   
  6.         部门编号:20   
  7.         PL/SQL 过程已成功完成。  
  1. ------------------ 雇员信息 ------------------  
  2.         雇员编号:7788  
  3.         雇员名称:SCOTT  
  4.         雇员职务:ANALYST  
  5.         雇员工资:3000  
  6.         部门编号:20  
  7.         PL/SQL 过程已成功完成。  

步骤4:修改雇员工资:
Sql代码 复制代码
  1. EXECUTE EMPLOYE.CHANGE_SAL(3800);  
  1. EXECUTE EMPLOYE.CHANGE_SAL(3800);  

结果为:
Sql代码 复制代码
  1. 修改工资完成!   
  2.         PL/SQL 过程已成功完成。  
  1. 修改工资完成!  
  2.         PL/SQL 过程已成功完成。  

步骤5:将修改的雇员信息存入EMP表
Sql代码 复制代码
  1. EXECUTE EMPLOYE.SAVE_EMPLOYE;  
  1. EXECUTE EMPLOYE.SAVE_EMPLOYE;  

结果为:
Sql代码 复制代码
  1. 雇员信息保存完成!   
  2.         PL/SQL 过程已成功完成。  
  1. 雇员信息保存完成!  
  2.         PL/SQL 过程已成功完成。  

说明:该包完成将EMP表中的某个雇员的信息取入内存记录变量,在记录变量中进行修改编辑,在确认显示信息正确后写回EMP表的功能。记录变量EMPLOYE用来存储取得的雇员信息,定义为私有变量,只能被包的内部模块访问。
  【练习1】为包增加修改雇员职务和部门编号的功能。

阶段训练
下面的训练通过定义和创建完整的包EMP_PK并综合运用本章的知识,完成对雇员表的插入、删除等功能,包中的主要元素解释如下所示。
Sql代码 复制代码
  1. 程序结构    类  型    说    明   
  2. V_EMP_COUNT 公有变量    跟踪雇员的总人数变化,插入、删除雇员的同时修改该变量的值   
  3. INIT    公有过程    对包进行初始化,初始化雇员人数和工资修改的上、下限   
  4. LIST_EMP    公有过程    显示雇员列表   
  5. INSERT_EMP  公有过程    通过编号插入新雇员   
  6. DELETE_EMP  公有过程    通过编号删除雇员   
  7. CHANGE_EMP_SAL  公有过程    通过编号修改雇员工资   
  8. V_MESSAGE   私有变量    存放准备输出的信息   
  9. C_MAX_SAL   私有变量    对工资修改的上限   
  10. C_MIN_SAL   私有变量    对工资修改的下限   
  11. SHOW_MESSAGE    私有过程    显示私有变量V_MESSAGE中的信息   
  12. EXIST_EMP   私有函数    判断某个编号的雇员是否存在,该函数被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等过程调用  
  1. 程序结构    类  型    说    明  
  2. V_EMP_COUNT 公有变量    跟踪雇员的总人数变化,插入、删除雇员的同时修改该变量的值  
  3. INIT    公有过程    对包进行初始化,初始化雇员人数和工资修改的上、下限  
  4. LIST_EMP    公有过程    显示雇员列表  
  5. INSERT_EMP  公有过程    通过编号插入新雇员  
  6. DELETE_EMP  公有过程    通过编号删除雇员  
  7. CHANGE_EMP_SAL  公有过程    通过编号修改雇员工资  
  8. V_MESSAGE   私有变量    存放准备输出的信息  
  9. C_MAX_SAL   私有变量    对工资修改的上限  
  10. C_MIN_SAL   私有变量    对工资修改的下限  
  11. SHOW_MESSAGE    私有过程    显示私有变量V_MESSAGE中的信息  
  12. EXIST_EMP   私有函数    判断某个编号的雇员是否存在,该函数被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等过程调用  

【训练1】  完整的雇员包EMP_PK的创建和应用。
步骤1:在SQL*Plus中登录SCOTT账户,输入以下包头和包体部分,按“执行”按钮编译:
Sql代码 复制代码
  1. CREATE OR REPLACE PACKAGE EMP_PK    
  2.         --包头部分   
  3.         IS  
  4.         V_EMP_COUNT NUMBER(5);                 
  5.         --雇员人数  
  6.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER);  --初始化  
  7.         PROCEDURE LIST_EMP;                        
  8.         --显示雇员列表  
  9. PROCEDURE INSERT_EMP(P_EMPNO        NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,   
  10.         P_SAL NUMBER);                         
  11.         --插入雇员  
  12.         PROCEDURE DELETE_EMP(P_EMPNO NUMBER);       --删除雇员  
  13.          PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);    
  14.         --修改雇员工资  
  15.         END EMP_PK;   
  16.         /CREATE OR REPLACE PACKAGE BODY EMP_PK   
  17.          --包体部分   
  18.         IS  
  19.         V_MESSAGE VARCHAR2(50); --显示信息  
  20. V_MAX_SAL NUMBER(7); --工资上限  
  21.         V_MIN_SAL NUMBER(7); --工资下限  
  22.         FUNCTION EXIST_EMP(P_EMPNO NUMBER)  RETURN  BOOLEAN; --判断雇员是否存在函数  
  23.         PROCEDURE SHOW_MESSAGE; --显示信息过程  
  24.         ------------------------------- 初始化过程 ----------------------------  
  25.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)    
  26.         IS    
  27.         BEGIN  
  28.          SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;   
  29. V_MAX_SAL:=P_MAX;   
  30.          V_MIN_SAL:=P_MIN;   
  31.          V_MESSAGE:='初始化过程已经完成!';   
  32.          SHOW_MESSAGE;    
  33.         END INIT;   
  34. ---------------------------- 显示雇员列表过程 ---------------------  
  35.         PROCEDURE LIST_EMP    
  36.          IS    
  37.         BEGIN  
  38. DBMS_OUTPUT.PUT_LINE('姓名       职务      工资');   
  39.         FOR emp_rec IN (SELECT * FROM EMP)   
  40.         LOOP   
  41.     DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,'')||RPAD(emp_rec.job,10,' ')||TO_CHAR(emp_rec.sal));   
  42.          END LOOP;   
  43.          DBMS_OUTPUT.PUT_LINE('雇员总人数'||V_EMP_COUNT);   
  44.         END LIST_EMP;   
  45. ----------------------------- 插入雇员过程 -----------------------------  
  46.         PROCEDUREINSERT_EMP(P_EMPNO     NUMBER,P_ENAMEVARCHAR2,P_JOB    VARCHAR2,P_SAL NUMBER)   
  47.          IS    
  48.         BEGIN  
  49.         IF NOT EXIST_EMP(P_EMPNO) THEN  
  50.         INSERT INTO EMP(EMPNO,ENAME,JOB,SAL)        VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);   
  51.         COMMIT;    
  52.         V_EMP_COUNT:=V_EMP_COUNT+1;   
  53.         V_MESSAGE:='雇员'||P_EMPNO||'已插入!';   
  54.         ELSE  
  55. V_MESSAGE:='雇员'||P_EMPNO||'已存在,不能插入!';   
  56.       END IF;   
  57.      SHOW_MESSAGE;    
  58.      EXCEPTION   
  59.     WHEN OTHERS THEN  
  60.      V_MESSAGE:='雇员'||P_EMPNO||'插入失败!';   
  61.      SHOW_MESSAGE;   
  62.      END INSERT_EMP;   
  63. --------------------------- 删除雇员过程 --------------------  
  64.          PROCEDURE DELETE_EMP(P_EMPNO NUMBER)    
  65.         IS    
  66.         BEGIN    
  67.         IF EXIST_EMP(P_EMPNO) THEN  
  68.         DELETE FROM EMP WHERE EMPNO=P_EMPNO;   
  69.         COMMIT;   
  70.          V_EMP_COUNT:=V_EMP_COUNT-1;   
  71.          V_MESSAGE:='雇员'||P_EMPNO||'已删除!';   
  72.          ELSE  
  73. V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能删除!';   
  74.     END IF;   
  75.     SHOW_MESSAGE;   
  76.      EXCEPTION   
  77.      WHEN OTHERS THEN  
  78.      V_MESSAGE:='雇员'||P_EMPNO||'删除失败!';   
  79.      SHOW_MESSAGE;   
  80.     END DELETE_EMP;   
  81. --------------------------------------- 修改雇员工资过程 ------------------------------------  
  82.         PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)    
  83.          IS    
  84.          BEGIN    
  85.          IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN  
  86.          V_MESSAGE:='工资超出修改范围!';   
  87.         ELSIF NOT EXIST_EMP(P_EMPNO) THEN  
  88.         V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能修改工资!';   
  89. ELSE  
  90.          UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;   
  91.         COMMIT;   
  92.         V_MESSAGE:='雇员'||P_EMPNO||'工资已经修改!';   
  93.         END IF;   
  94.         SHOW_MESSAGE;   
  95.         EXCEPTION   
  96.          WHEN OTHERS THEN  
  97.          V_MESSAGE:='雇员'||P_EMPNO||'工资修改失败!';   
  98.          SHOW_MESSAGE;   
  99.          END CHANGE_EMP_SAL;   
  100. ---------------------------- 显示信息过程 ----------------------------  
  101.          PROCEDURE SHOW_MESSAGE    
  102.         IS    
  103.         BEGIN  
  104.          DBMS_OUTPUT.PUT_LINE('提示信息:'||V_MESSAGE);   
  105.         END SHOW_MESSAGE;   
  106. ------------------------ 判断雇员是否存在函数 -------------------  
  107.          FUNCTION EXIST_EMP(P_EMPNO NUMBER)   
  108.          RETURN BOOLEAN    
  109.          IS  
  110.         V_NUM NUMBER; --局部变量  
  111.         BEGIN  
  112.         SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;   
  113. IF V_NUM=1 THEN    
  114.            RETURN TRUE;   
  115.          ELSE  
  116.          RETURN FALSE;   
  117.         END IF;    
  118.         END EXIST_EMP;   
  119.         -----------------------------  
  120.         END EMP_PK;  
  1. CREATE OR REPLACE PACKAGE EMP_PK   
  2.         --包头部分   
  3.         IS  
  4.         V_EMP_COUNT NUMBER(5);                
  5.         --雇员人数  
  6.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER);  --初始化  
  7.         PROCEDURE LIST_EMP;                       
  8.         --显示雇员列表  
  9. PROCEDURE INSERT_EMP(P_EMPNO        NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,  
  10.         P_SAL NUMBER);                        
  11.         --插入雇员  
  12.         PROCEDURE DELETE_EMP(P_EMPNO NUMBER);       --删除雇员  
  13.          PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER);   
  14.         --修改雇员工资  
  15.         END EMP_PK;  
  16.         /CREATE OR REPLACE PACKAGE BODY EMP_PK  
  17.          --包体部分   
  18.         IS  
  19.         V_MESSAGE VARCHAR2(50); --显示信息  
  20. V_MAX_SAL NUMBER(7); --工资上限  
  21.         V_MIN_SAL NUMBER(7); --工资下限  
  22.         FUNCTION EXIST_EMP(P_EMPNO NUMBER)  RETURN  BOOLEAN; --判断雇员是否存在函数  
  23.         PROCEDURE SHOW_MESSAGE; --显示信息过程  
  24.         ------------------------------- 初始化过程 ----------------------------  
  25.         PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER)   
  26.         IS   
  27.         BEGIN  
  28.          SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP;  
  29. V_MAX_SAL:=P_MAX;  
  30.          V_MIN_SAL:=P_MIN;  
  31.          V_MESSAGE:='初始化过程已经完成!';  
  32.          SHOW_MESSAGE;   
  33.         END INIT;  
  34. ---------------------------- 显示雇员列表过程 ---------------------  
  35.         PROCEDURE LIST_EMP   
  36.          IS   
  37.         BEGIN  
  38. DBMS_OUTPUT.PUT_LINE('姓名       职务      工资');  
  39.         FOR emp_rec IN (SELECT * FROM EMP)  
  40.         LOOP  
  41.     DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,'')||RPAD(emp_rec.job,10,' ')||TO_CHAR(emp_rec.sal));  
  42.          END LOOP;  
  43.          DBMS_OUTPUT.PUT_LINE('雇员总人数'||V_EMP_COUNT);  
  44.         END LIST_EMP;  
  45. ----------------------------- 插入雇员过程 -----------------------------  
  46.         PROCEDUREINSERT_EMP(P_EMPNO     NUMBER,P_ENAMEVARCHAR2,P_JOB    VARCHAR2,P_SAL NUMBER)  
  47.          IS   
  48.         BEGIN  
  49.         IF NOT EXIST_EMP(P_EMPNO) THEN  
  50.         INSERT INTO EMP(EMPNO,ENAME,JOB,SAL)        VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL);  
  51.         COMMIT;   
  52.         V_EMP_COUNT:=V_EMP_COUNT+1;  
  53.         V_MESSAGE:='雇员'||P_EMPNO||'已插入!';  
  54.         ELSE  
  55. V_MESSAGE:='雇员'||P_EMPNO||'已存在,不能插入!';  
  56.       END IF;  
  57.      SHOW_MESSAGE;   
  58.      EXCEPTION  
  59.     WHEN OTHERS THEN  
  60.      V_MESSAGE:='雇员'||P_EMPNO||'插入失败!';  
  61.      SHOW_MESSAGE;  
  62.      END INSERT_EMP;  
  63. --------------------------- 删除雇员过程 --------------------  
  64.          PROCEDURE DELETE_EMP(P_EMPNO NUMBER)   
  65.         IS   
  66.         BEGIN   
  67.         IF EXIST_EMP(P_EMPNO) THEN  
  68.         DELETE FROM EMP WHERE EMPNO=P_EMPNO;  
  69.         COMMIT;  
  70.          V_EMP_COUNT:=V_EMP_COUNT-1;  
  71.          V_MESSAGE:='雇员'||P_EMPNO||'已删除!';  
  72.          ELSE  
  73. V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能删除!';  
  74.     END IF;  
  75.     SHOW_MESSAGE;  
  76.      EXCEPTION  
  77.      WHEN OTHERS THEN  
  78.      V_MESSAGE:='雇员'||P_EMPNO||'删除失败!';  
  79.      SHOW_MESSAGE;  
  80.     END DELETE_EMP;  
  81. --------------------------------------- 修改雇员工资过程 ------------------------------------  
  82.         PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER)   
  83.          IS   
  84.          BEGIN   
  85.          IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN  
  86.          V_MESSAGE:='工资超出修改范围!';  
  87.         ELSIF NOT EXIST_EMP(P_EMPNO) THEN  
  88.         V_MESSAGE:='雇员'||P_EMPNO||'不存在,不能修改工资!';  
  89. ELSE  
  90.          UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO;  
  91.         COMMIT;  
  92.         V_MESSAGE:='雇员'||P_EMPNO||'工资已经修改!';  
  93.         END IF;  
  94.         SHOW_MESSAGE;  
  95.         EXCEPTION  
  96.          WHEN OTHERS THEN  
  97.          V_MESSAGE:='雇员'||P_EMPNO||'工资修改失败!';  
  98.          SHOW_MESSAGE;  
  99.          END CHANGE_EMP_SAL;  
  100. ---------------------------- 显示信息过程 ----------------------------  
  101.          PROCEDURE SHOW_MESSAGE   
  102.         IS   
  103.         BEGIN  
  104.          DBMS_OUTPUT.PUT_LINE('提示信息:'||V_MESSAGE);  
  105.         END SHOW_MESSAGE;  
  106. ------------------------ 判断雇员是否存在函数 -------------------  
  107.          FUNCTION EXIST_EMP(P_EMPNO NUMBER)  
  108.          RETURN BOOLEAN   
  109.          IS  
  110.         V_NUM NUMBER; --局部变量  
  111.         BEGIN  
  112.         SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO;  
  113. IF V_NUM=1 THEN   
  114.            RETURN TRUE;  
  115.          ELSE  
  116.          RETURN FALSE;  
  117.         END IF;   
  118.         END EXIST_EMP;  
  119.         -----------------------------  
  120.         END EMP_PK;  
结果为:
Sql代码 复制代码
  1. 程序包已创建。   
  2.         程序包主体已创建。  
  1. 程序包已创建。  
  2.         程序包主体已创建。  

步骤2:初始化包:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2. EXECUTE EMP_PK.INIT(6000,600);  
  1. SET SERVEROUTPUT ON  
  2. EXECUTE EMP_PK.INIT(6000,600);  

显示为:
Sql代码 复制代码
  1. 提示信息:初始化过程已经完成!  
  1. 提示信息:初始化过程已经完成!  

步骤3:显示雇员列表:
Sql代码 复制代码
  1. EXECUTE EMP_PK.LIST_EMP;  
  1. EXECUTE EMP_PK.LIST_EMP;  

显示为:
Sql代码 复制代码
  1. 姓名          职务          工资   
  2.         SMITH       CLERK       1560   
  3.         ALLEN       SALESMAN    1936   
  4.         WARD        SALESMAN    1830   
  5.         JONES       MANAGER     2975   
  6.         ...   
  7.         雇员总人数:14   
  8.         PL/SQL 过程已成功完成。  
  1. 姓名          职务          工资  
  2.         SMITH       CLERK       1560  
  3.         ALLEN       SALESMAN    1936  
  4.         WARD        SALESMAN    1830  
  5.         JONES       MANAGER     2975  
  6.         ...  
  7.         雇员总人数:14  
  8.         PL/SQL 过程已成功完成。  

步骤4:插入一个新记录:
Sql代码 复制代码
  1. EXECUTE EMP_PK.INSERT_EMP(8001,'小王','CLERK',1000);  
  1. EXECUTE EMP_PK.INSERT_EMP(8001,'小王','CLERK',1000);  

显示结果为:
Sql代码 复制代码
  1. 提示信息:雇员8001已插入!   
  2. PL/SQL 过程已成功完成。  
  1. 提示信息:雇员8001已插入!  
  2. PL/SQL 过程已成功完成。  

步骤5:通过全局变量V_EMP_COUNT查看雇员人数:
Sql代码 复制代码
  1. BEGIN  
  2. DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);   
  3. END;  
  1. BEGIN  
  2. DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT);  
  3. END;  

显示结果为:
Sql代码 复制代码
  1. 15   
  2. PL/SQL 过程已成功完成。  
  1. 15  
  2. PL/SQL 过程已成功完成。  

步骤6:删除新插入记录:
Sql代码 复制代码
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  

显示结果为:
Sql代码 复制代码
  1. 提示信息:雇员8001已删除!   
  2.         PL/SQL 过程已成功完成。  
  1. 提示信息:雇员8001已删除!  
  2.         PL/SQL 过程已成功完成。  

再次删除该雇员:
Sql代码 复制代码
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  
  1. EXECUTE EMP_PK.DELETE_EMP(8001);  

结果为:
Sql代码 复制代码
  1. 提示信息:雇员8001不存在,不能删除!  
  1. 提示信息:雇员8001不存在,不能删除!  

步骤7:修改雇员工资:
Sql代码 复制代码
  1. EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);  
  1. EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000);  

显示结果为:
Sql代码 复制代码
  1. 提示信息:工资超出修改范围!   
  2.         PL/SQL 过程已成功完成。  
  1. 提示信息:工资超出修改范围!  
  2.         PL/SQL 过程已成功完成。  

步骤8:授权其他用户调用包:
如果是另外一个用户要使用该包,必须由包的所有者授权,下面授予STUDEN账户对该包的使用权:
Sql代码 复制代码
  1. GRANT EXECUTE ON EMP_PK TO STUDENT;  
  1. GRANT EXECUTE ON EMP_PK TO STUDENT;  

每一个新的会话要为包中的公用变量开辟新的存储空间,所以需要重新执行初始化过程。两个会话的进程互不影响。
步骤9:其他用户调用包。
启动另外一个SQL*Plus,登录STUDENT账户,执行以下过程:
Sql代码 复制代码
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);  
  1. SET SERVEROUTPUT ON  
  2.         EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700);  

结果为:
Sql代码 复制代码
  1. 提示信息:初始化过程已经完成!   
  2.         PL/SQL 过程已成功完成。  
  1. 提示信息:初始化过程已经完成!  
  2.         PL/SQL 过程已成功完成。  

说明:在初始化中设置雇员的总人数和修改工资的上、下限,初始化后V_EMP_COUNT为14人,插入雇员后V_EMP_COUNT为15人。V_EMP_COUNT为公有变量,所以可以在外部程序中使用DBMS_OUTPUT.PUT_LINE输出,引用时用EMP_PK.V_EMP_COUNT的形式,说明所属的包。而私有变量V_MAX_SAL和V_MIN_SAL不能被外部访问,只能通过内部过程来修改。同样,EXIST_EMP和SHOW_MESSAGE也是私有过程,也只能在过程体内被其他模块引用。
注意:在最后一个步骤中,因为STUDENT模式调用了SCOTT模式的包,所以包名前要增加模式名SCOTT。不同的会话对包的调用属于不同的应用,所以需要重新进行初始化。
分享到:
评论

相关推荐

    oracle存储过程学习经典入门

    Oracle 存储过程学习经典入门 Oracle 存储过程学习目录是 Oracle 存储过程学习的基础知识,了解 Oracle 存储过程的基本语法、基础知识和一些常见问题的解决方法是非常重要的。本文将从 Oracle 存储过程的基础知识...

    ORACLE存储过程学习

    ### ORACLE存储过程学习知识点详解 #### 一、存储过程概述 存储过程是数据库中预编译好的一组SQL语句,它可以实现复杂的数据处理逻辑,提高应用开发效率,并且能够增强应用程序的安全性。Oracle存储过程使用PL/SQL...

    ORACLE存储过程学习源码

    这个"ORACLE存储过程学习源码"集合包含了从基础到高级的30个示例,是学习和掌握Oracle存储过程的理想资源。下面,我们将深入探讨存储过程的基本概念、结构、类型,以及如何通过这些源码进行学习。 1. **存储过程的...

    oracle存储过程学习经典

    ### Oracle存储过程学习经典 #### Oracle存储过程基础知识与实践 Oracle存储过程是SQL与PL/SQL结合的强大功能,用于封装复杂的数据操作逻辑于数据库内部,从而实现高效的事务处理和数据管理。以下是对Oracle存储...

    oracle存储过程学习经典[语法+实例+调用].doc

    ### Oracle存储过程学习经典知识点详解 #### Oracle存储过程概述与基础知识 存储过程是数据库中预编译的一系列SQL和PL/SQL语句的集合,它提供了执行复杂操作的能力,如事务处理、数据处理和错误处理。Oracle存储...

    oracle存储过程学习经典(实例)

    这个"Oracle存储过程学习经典(实例)"资源显然是为初学者设计的,旨在帮助他们掌握如何创建、执行和管理存储过程。 存储过程在数据库管理中扮演着关键角色,它可以提升系统的性能,通过减少网络流量和提供预编译的...

    oracle 存储过程学习经典

    ### Oracle存储过程学习经典知识点详解 #### 一、Oracle存储过程概述 - **定义**: 存储过程是在数据库中预编译并存储的一段SQL或PL/SQL代码块,它可以包含复杂的逻辑处理,用于实现特定的功能。存储过程提高了代码...

    oracle存储过程学习经典[语法+实例+调用]

    ### Oracle存储过程学习经典知识点详解 #### 一、Oracle存储过程概述 **存储过程**是在数据库中预先定义并编译好的一系列SQL语句或PL/SQL代码块,它可以接受输入参数,输出参数,并能实现复杂的业务逻辑处理。通过...

    Oracle 存储过程学习文档

    ### Oracle存储过程学习文档知识点详解 #### 一、Oracle存储过程概述 **1.1 存储过程定义:** Oracle存储过程是一种存储在数据库中的PL/SQL代码块,它可以接收输入参数并返回输出参数。存储过程能够执行复杂的业务...

    oracle存储过程学习经典[语法+实例+调用].pdf

    ### Oracle存储过程学习经典知识点详解 #### 一、创建存储过程 存储过程是数据库中预编译的一段SQL代码,可以提高程序的可维护性和执行效率。在Oracle中,可以通过`CREATE OR REPLACE PROCEDURE`语句来创建存储过程...

    oracle存储过程学习资料

    在本学习资料中,你将深入理解Oracle存储过程的创建、调用、调试以及优化等多个方面。 1. **存储过程的创建**: Oracle存储过程通过`CREATE PROCEDURE`语句来定义。你可以指定输入参数、输出参数、输入输出参数,...

    oracle存储过程学习

    在"Oracle存储过程学习"这个主题中,我们可以深入探讨以下几个关键知识点: 1. **定义与创建**: 存储过程通过`CREATE PROCEDURE`语句创建。例如: ```sql CREATE OR REPLACE PROCEDURE proc_name (param1 ...

    oracle 存储过程学习总结

    Oracle存储过程学习总结涵盖了Oracle中存储过程的编写与应用,涉及到字符串处理、游标使用、PL/SQL编程等方面的知识点。 首先,字符串处理是存储过程中常见的操作。文章中提到了多个内置函数,如CONCAT用于连接字符...

    oracle存储过程学习笔记(四)

    在本篇“Oracle存储过程学习笔记(四)”中,我们将深入探讨存储过程的概念、创建、执行以及在实际应用中的优势。 1. **存储过程的概念** 存储过程是一组预先编译的SQL和PL/SQL语句,存储在数据库服务器中。当需要...

    oracle存储过程学习实例文档 创建调用

    在这个“Oracle存储过程学习实例文档”中,我们将深入探讨如何创建存储过程,以及如何在Java应用程序中调用这些过程。 1. **创建Oracle存储过程** 创建存储过程的基本语法如下: ```sql CREATE OR REPLACE ...

    oracle存储过程学习笔记

    1.基本结构 CREATE OR REPLACE PROCEDURE 存储过程名字 ( 参数1 IN NUMBER, 参数2 IN NUMBER ) IS 变量1 INTEGER :=0; 变量2 DATE; BEGIN END 存储过程名字

    oracle存储过程学习经典入门.rar_oracle

    这个压缩包文件"oracle存储过程学习经典入门.rar_oracle"显然包含了帮助初学者理解并掌握Oracle存储过程的基础教程。下面将详细讲解Oracle存储过程的相关知识点。 首先,存储过程是预编译的SQL语句集合,它在数据库...

Global site tag (gtag.js) - Google Analytics