PL/SQL数据库开发那点事

PL/SQL数据库开发那点事-->编程,存储程序

在SQL*plus 中编写PL/SQL程序,并在SQL*plus 中执行它, PL/SQL块的代码就存放在SQL*plus的缓冲区中。
如果在SQL*plus 中执行了其他的SQL语句或PL/SQL块,缓冲区中就会存放新的代码,原来的PL/SQL块就会被从缓冲区中清除出去。
这种没有名称只是临时存放在缓冲区中的PL/SQL块叫做匿名块。
匿名块就是没有名字的PL/SQL块,它仅存放在缓冲区中,只能在当前SQL*plus环境中执行。
如果希望PL/SQL块能随时被调用执行,并且能被数据库用户共享,就需要创建存储程序
存储程序是有名字的PL/SQL块,用户可以根据它的名字进行多次调用。

存储程序在创建时经过了编译优化,被存放在数据库中,任何用户只要有适当的权限,就可以调用它。
而且在调用时无需再进行编译,因此能以很快的速度执行。
与匿名块相比,存储程序是作为数据库对象存储在数据库中的,因此,首先要在数据库中创建存储程序。

存储程序的调用可以在SQL语句中、应用程序中、SQL*plus 中以及其他PL/SQL块中进行。
在第一次被调用时,存储程序的代码被装载到系统全局区的共享池中,以后再次调用时直接从共享池中取出代码即可执行。
存储程序与前面介绍的子程序的区别在于子程序是完成某个特定功能的程序段,它本身并不能单烛执行,只能作为一个模块,在一个PL/SQL块内部被调用执行。
而存储程序是一个可单独执行的程序,它可以包含多个子程序,可以在SQL语句中、应用程序中、SQL*plus 中以及其他PL/SQL块中被调用执行。
存储程序的形式包括:存储过程、存储函数、触发器和程序包等。

存储过程

如果用户要在自己的模式中创建存储过程,需要具有CREATE PROCEDURE系统权限,如果要在其他用户的模式中创建存储过程,则需要具有CREATE ANY PROCEDURE 系统权限
创建存储过程的语法为:

CREATE OR REPLACE PROCEDURE 过程名(参数1,参数2 ...)

AUTHID CURRENT_USER | DEFINER

AS

  声明部分

BEGIN

  可执行部分

EXCEPTION

  异常处理部分

END;

其中OR REPLACE选项的作用是当同名的存储过程存在时,首先将其删除,再创建新的存储过程。
当然,条件是当前用户具有删除原存储过程的权限。

存储过程在创建过程中已经进行了编译和优化。
如果需要对存储过程进行修改,不能直接修改它的源代码,只能执行CREATE命令重新创建。
存储过程、存储函数、程序包都是这样的情况。
存储过程可以带有参数,这样在调用存储过程时就需要指定相应的实际参数。

如果没有参数,过程名后面的圆括号和参数列表就可以省略了。
每个参数的定义格式为:

参数名 参数传递模式 数据类型 := 默认值

参数各定义中各部分的用法与子程序中的参数完全相同。

AUTHID 选项用来规定存储过程执行时的权限。

这个选项有两个可选值,即CURRENT_USERDEFINER ,二者只能选择其中一个。
过程的执行者和创建者可能不是同一个用户,如果使用CURRENT_USER创建存储过程,那么在调用时,该过程以当前登录用户的身份执行。
为此,过程的创建者必须授予当前用户执行该过程的权限。

如果以DEFINER创建存储过程,那么在调用时,该过程将以创建者身份执行,这是创建存储过程时默认的选项。

授予其他用户执行存储过程的权限:

GRANT EXECUTE ON 过程名称 TO 其他用户;

用户在执行其他用户的过程时,要在过程名前加上模式名。

在存储过程中可以定义变量、类型、子程序、游标等元素,定义的方法与在匿名块中完全相同。

这里不再详细描述。

存储过程的声明部分开始于关键宇AS ,结束于关键字BEGIN ,而且不需要使用关键字DECLARE 。
存储过程的可执行部分是它的主要部分,它可以包含SQL语句和流控制语句,是存储过程功能的集中体现。
异常处理部分用来处理存储过程在执行过程中可能出现的错误。

例如,下面的代码用来创建存储过程total_income ,它的功能是计算某部门员工的总收入。
这个过程有一个参数,代表部门编号,并指定了默认值。
这样,在调用时,如果提供了参数,则计算指定部门的数据,否则将计算所有员工的数据。

create or replace
PROCEDURE total_income(d_no IN integer:=0)
AUTHID DEFINER
AS
total number;
BEGIN
if d_no=0 then --表示所有部门
SELECT sum(sal+nvl(comm, 0)) INTO total FROM emp;
else --仅表示指定的部门
SELECT sum(sal+nvl(comm, 0)) INTO total
FROM emp
WHERE deptno=d_no;
END if;
dbms_output.put_line ('总收入:'|| total);
END;

存储过程创建以后,就可以随时调用执行了。

在SQL*plus 中调用存储过程的命令是EXECUTE ,命令的使用格式为:
EXECUTE 过程名(实际参数)

例如,要计算部门10的员工总收入和应缴的税,则可以以下形式调用刚才创建的存储过程total income 。

EXECUTE total_income(10)

如果要在一个PL/SQL块中调用存储过程,则不需要EXECUTE命令,只要通过过程名和实际参数就可以调用,调用的格式为:

过程名(实际参数);

每个用户都可以执行自己创建的存储过程,如果要执行其他用户的存储过程,则需要具有对该存储过程的EXECUTE权限。
为此,存储过程的所有者要将EXECUTE权限授予这个用户。
授予EXECUTE权限的语句格式为:

GRANT EXECUTE ON 过程名 TO 用户; 

例如,存储过程total_income的所有者要将它的执行权限授予用户scott,则可以执行下面的SQL语句:

GRANT EXECUTE ON total_income TO scott;

如果要删除一个存储过程,可以执行DROP命令,这个命令的格式为:
DROP PROCEDURE 过程名

存储函数

存储函数也是一种存储程序,它被创建后便存储在数据库中,用户可以直接调用。

存储函数与存储过程的区别在于,存储函数必须向调用环境返回一个执行结果。
一般情况我们是把存储函数作为一个表达式来使用的,它可用于普通表达式能够使用的场合,这是因为每个函数都有一个返回值,在调用存储函数时,这个返回值便是存储函数的执行结果。
例如,可以将存储函数赋给一个变量,或者将这个函数与另一个表达式进行计算等。
创建存储函数的语法格式为:

CREATE OR REPLACE FUNCTION 函数名(参数1,参数2 ...)RETURN 返回类型

AUTHID CURRENT_USER | DEFINER

AS

  声明部分

BEGIN

  可执行部分

EXCEPTION

  异常处理部分

END;

可以看出,创建存储函数的格式与创建存储过程的格式大致相同,只有三个不同的地方,第一,用FUNCTION关键字代替了PROCEDURE关键字,以表明创建的对象是存储函数,第二,在参数列表之后用RETURN关键字规定了存储函数返回值的类型, 第三,在存储函数的可执行部分至少有一条RETURN语句,将执行结果返回给调用者。
在存储函数的可执行部分中,可能会出现多条RETURN语句,用于向调用者返回不同的数据,但是经过逻辑处理后,只能有一条RETURN语句被执行,保证从存储函数中返回一个确定的数据,这样就符合了程序的“单出口”的原则。
如果用户要在自己的模式中创建存储函数,需要具有CREATE FUNCTION的系统权限,如果要在其他模式中创建存储函数,则需要具有CREATE ANY FUNCTION的系统权限
例如,下面的存储函数用来计算每个员工的总收入。

这个函数有两个参数,即工资和奖金,它的功能是求出工资和奖金之和,然后将结果返回。
创建这个函数的语句为:

create or replace
FUNCTION total_income_1(sal number,comm number)
RETURN number
AS
result number:=0;
BEGIN
result:=sal+nvl(comm,0);
RETURN result;
END;

如果要利用这个存储函数求员工的总收入,可以将这个函数用在SELECT语句中,作为SELECT语句的一个表达式,并且向它传递实际参数,最后得到它的计算结果。
例如:

SELECT ename,total_income_1(sal,comm) as total FROM emp;

再比如,下面的存储函数用于计算员工应缴的个人所得税,这个函数以部门号为参数,计算该部门中全部员工的所得税总和。
假设税率为3% ,该函数用SUM 函数计算全体员工的工资总和,然后乘以3% ,并将最后的结果返回。
函数的代码如下所示:

create or replace
FUNCTION tax_per_depart(dno integer) RETURN number
AS
result number:=0;
BEGIN
SELECT sum(sal)*0.03 INTO result FROM emp
WHERE deptno=dno
GROUP BY deptno;
RETURN result;
END;

与其他存储函数一样,这个函数可以用在SELECT语句中,也可以在其他匿名块存储过存储函数中调用执行。
例如,在下面的匿名块中调用了该函数,计算部门20 的所得税。

DECLARE
dno integer;
total_tax number;
BEGIN
dno :=20;
total_tax:=tax_per_depart(dno);
dbms_output.put_line('Total tax of department' || dno || 'is:' || total_tax);
END;

每个用户都可以直接调用自己创建的存储函数,如果要调用其他用户的存储函数,则需要具有对相应存储函数的EXECUTE权限。
为此,存储函数的所有者要将EXECUTE权限授予适当的用户。
授予EXECUTE权限的语句格式为:

GRANT EXECUTE ON 函数名 TO 用户名;

例如,存储函数total_income的所有者要将它的执行权限授予用户scott,则可以执行下面的SQL语句:

GRANT EXECUTE ON total_income TO scott;

如果要删除一个存储函数,可以执行DROP命令,这个命令的格式为:

DROP FUNCTION 函数名;

总之,存储过程和存储函数都是存储程序,它们的区别在于存储过程没有返回值,只能被单独调用执行,在功能上类似于一条命令,而存储函数有返回值,可以用在SELECT语句和运算表达式中,它的作用相当于一个普通的表达式。
在存储过程和存储函数中都可以定义子程序,这里把重点放在了存储过程和存储函数本身的使用上,对子程序在存储过程和存储函数中的用法没有进行描述,实际上这也是很简单的。

程序包

程序包是一种Oracle数据库对象,它是一组逻辑上相关的数据类型、变量、过程、函数和游标等的集合。

程序包被创建后,存储在数据库中,用户可以直接使用包中的数据类型和变量,也可以直接调用包中的过程和函数。
程序包有两种形式,一种是用户根据需要创建的程序包,一种是系统预定义的程序包。

这里介绍自定义程序包的创建、使用、删除等操作,以及预定义程序包的使用方法
用户可以根据需要创建自己的程序包。

在程序包中可以定义数据类型、变量、过程、函数、异常和游标等元素,这些元素具有全局的特性,可以在程序包中使用,也可以在程序包之外使用

一个程序包由两部分组成:程序包的头部和包体。

其中头部用来定义类型、变量、异常、声明游标、过程和函数,它的作用相当于程序包的接口。
在包体中可以利用头部的类型定义变量,定义过程、游标和函数的代码。
在创建程序包时,头部和包体是分别创建的,并且头部必须在包体之前创建

程序包创建之后,如果要对其功能进行修改,这时只需修改包体的代码即可,不用修改头部,仅当需要改变参数类型、参数个数等信息时,才需要修改程序包的头部。
创建程序包头部的命令是CREATE PACKAGE ,这条命令的语法格式为:
CREATE [OR REPLACE] PACKAGE 包名称

AUTHID CURRENT_USER | DEFINER

AS

  类型的定义;

  变量的定义;

  子程序的声明;

  游标的声明;

  异常的声明;

END;

其中OR REPLACE选项的作用是当指定的包已经存在时重新创建它。

AUTHID选项用来规定程序包以哪个用户的身份执行。
这个选项有两个可选值,即CURRENT_USER和DEFINER,二者只能选择其中一个。
子程序的声明就是定义过程和函数的原型,即子程序的名称、参数和返回值,不包含它的代码部分。
类型定义部分允许用户根据需要创建自己的数据类型。
例如,要对部门员工的总收入和所得税进行统计,为此需要编写一个程序包。

在程序包中首先定义了一个记录类型total ,然后声明了一个函数tax_per_depart ,用来统计某个部门的所得税,过程total_ per_depart用来统计各个部门的员工总收入。
最后还定义了一个游标c1。

需要注意的是,在程序包的头部定义游标时需要指定它的返回类型。
以下是创建程序包employee头部的代码:

create or replace
PACKAGE employee
AS
type total is record(
dno emp.deptno%type,
total_income number
);
function tax_per_depart(dno integer) RETURN number;
procedure total_per_depart;
cursor c1 RETURN total;
END;

程序包的包体是对头部的实现,主要用来定义过程和函数的可执行代码。

创建包体的命令是CREATE PACKAGE BODY ,这条命令的语法格式为:
CREATE [OR REPLACE] PACKAGE BODY 包名

AS

  游标的实现;

  子程序的实现;

END;

其中包名与创建头部时使用的名字完全相同。

游标的实现是指定游标中所使用的SELECT语句。
子程序的实现是写出过程和函数的代码,过程和函数的编写方法与以前介绍的方法完全相同。
以下是创建程序包employee的包体的代码。

CREATE OR REPLACE PACKAGE BODY employee
AS
CURSOR c1 RETURN total is --定义游标
SELECT deptno, sum(sal) FROM emp GROUP BY deptno;
FUNCTION tax_per_depart(dno integer) --定义函数tax_per_depart
RETURN number
AS
result number;
BEGIN
SELECT SUM(sal)*0.03 INTO result FROM emp
WHERE deptno=dno;
RETURN result;
END; --函数tax_per_depart结束
PROCEDURE total_per_depart --定义过程total_per_depart
AS
depart TOTAL;
BEGIN
OPEN c1;
FETCH c1 INTO depart; --利用取出游标中的数据
WHILE c1%FOUND LOOP
DBMS_OUTPUT.PUT_LINE('部门'||depart.dno||'总收入'||depart.total_income);
FETCH c1 INTO depart;
END LOOP;
CLOSE c1;
END;
END;

定义了程序包employee后,用户就可以在PL/SQL块或者SQL*Plus 中使用这个包中的类型、游标、变量、过程和函数了,使用的方法为:

包名.元素名

例如,要利用程序包employee 中的过程total_per_depart统计各个部门员工的总收入,在SQL*Plus 中调用这个过程:

exec employee.total_per_depart

再比如,在一个匿名块中调用程序包employee 中的函数tax_per_depart ,计算部门20的所得税,这个匿名块的代码为:

DECLARE
dno integer;
total_tax number;
BEGIN
dno := 20;
total_tax:=employee.tax_per_depart(dno);
dbms_output.put_line('Total tax of department '||dno||' is: '||total_tax );
END;

如果一个程序包不再需要,我们可以将其从数据库中删除。

删除程序包时,可以选择只删除包体,或者删除整个包。
删除整个程序包的命令是DROP PACKAGE ,它的格式为:

DROP PACKAGE 包名;

这样,程序包的头部和包体都将从数据库中被删除。
如果只删除包体,相应的命令为DROP PACKAGE BODY ,它的格式为:

DROP PACKAGE BODY 包名;

系统预定义的程序包

Oracle提供了一些预定义的程序包,利用这些包可以完成一些复杂的操作。

这些程序包提供了一些常用的类型、变量、过程和函数,用户可以在PL/SQL块和应用程序中直接使用它们。
正确地使用这些预定义的程序包,可以使开发工作达到事半功倍的效果。

常用的预定义程序包及其用途如下所示:

DBMS_OUTPUT  显示基本的输入输出功能

UTL_FILE  对操作系统文件进行读,写等操作

DBMS_SQL  执行DDL语句

DBMS_PIPE  用于在两个进程间以管道方式进行通信

DBMS_JOB  管理数据库中的作业

下面将对最常用的程序包DBMS_OUTPUT 、UTL_FILE和DBMS_SQL做简单的介绍。

1. DBMS_OUTPUT程序包

DBMS_OUTPUT包的功能是将PL/SQL块的执行结果显示在屏幕上,这种输出操作是通过缓冲区来完成的。

SQL*Plus 为存储程序、PL/SQL块、触发器的执行提供了-个缓冲区,用于存放程序执行期间所产生的数据,这个缓冲区以“先进先出”的方式管理其中的数据。
在默认情况下, PL/SQL块的执行结果是输出到缓冲区里的,如果进行一些特殊的设置,缓冲区中的数据就会输出到屏幕上,然后从缓冲区中清除。
DBMS_OUTPUT包提供了对缓冲区进行设置读和写等操作的功能,它提供了一系列的过程和函数,分别对缓冲区进行设置、读和写等操作。
用户利用DBMS_ OUTPUT包中的过程或函数可以向缓冲区中写人数据,也可以从缓冲区中读数据
缓冲区的设置操作主要包括使其可用和不可用等操作。

使缓冲区不可用的过程是DISABLE,这个过程可以在SQL*Plus 中以如下形式执行:
EXEC dbms_output.disable

如果要在存储程序、PL/SQL块和触发器中调用这个过程,则不需要EXEC命令,可以直接调用执行。
与DISABLE相对的操作是ENABLE过程,它可以使缓冲区可用,并且可以设置缓冲区的大小。
它的调用形式为:

EXEC dbms_output.enable(缓冲区的大小)

如果在调用这个过程时不指定任何参数,则结果是使缓冲区可用,并将其大小设置为默认大小,即20 000字节。
例如,要将缓冲区的大小设置为1024字节,这个过程的调用形式为:

exec dbms_output.enable(1024)

缓冲区的写操作指的是向缓冲区中写入数据,目前允许的数据类型有数字型、字符串型和日期型。
写操作涉及的过程有以下几个:
PUT (参数):将指定的参数写入缓冲区。
PUT_LINE (参数):将指定的参数写入缓冲区,并在行末写一个换行符
NEW_LINE :在缓冲区中当前位置处写一个换行符。
缓冲区中的数据是以行的形式组织的,每行最多存储255个字符,一行写满时,自动从下一行开始继续写。
由于缓冲区的大小有限,写数据的原则是“先进先出”,当缓冲区写满时,如果还要继续写,那么最先写入缓冲区中的数据就会被从缓冲区中清除出去,以便腾出空间容
纳新数据。
PUT和PUT_LINE过程的作用都是向缓冲区当前位置写入一行数据,它们之间的区别是,PUT_LINE在写完数据后在当前行的末尾写入一个换行符,而PUT过程不写入换行符。
过程NEW_LINE 的作用仅仅是在缓冲区当前位置处写入一个换行符
实际上,调用-次过程PUT_LINE ,相当于先调用一次过程PUT ,然后再调用一次过程NEW_LINE 。
如果要使缓冲区中的数据显示在显示器上,必须使选项SERVEROUTPUT有效,这个选项的作用就是使缓冲区中的数据可以输出到屏幕上。
为了使这个选项有效,在SQL*Plus 中执行SET命令:

SET serveroutput ON

这个选项的另一个可选值是OFF ,它的作用正好与ON相反。

为了说明这几个过程的用法,首先观察下面这个PL/SQL块的执行情况:

DECLARE
data1 integer := 100;
data2 varchar2(10) := 'Hello' ;
data3 date DEFAULT sysdate;
BEGIN
dbms_output.put(data1);
dbms_output.put_line(data2);
dbms_output.put_line(data3);
end;

缓冲区的读操作是指将缓冲区中的数据以行的形式读出来。

与缓冲区的读操作有关的过程有两个:
• GET_LINE :从缓冲区中读一行。
• GET_LINES :从缓冲区中读多行。
过程GET_LINE的作用是将目前缓冲区中最先写入的一行数据读出,并将这一行数据从缓冲区中删除。
它的调用形式为:

GET_LINE(变量,状态)

其中变量用于存放从缓冲区中读出的数据,它的类型必须与要读的数据一致。

状态也是一个变量,用来表示本次读操作是否成功,它的传递模式为OUT 。
在这个过程执行结束后,如果状态变量的值为0 ,表示成功,如果为1 ,则表示缓冲区中没有数据。
过程GET_LINES 的作用是将目前缓冲区中最先写入的几行数据读出,并将它们从缓冲区中删除。
它的调用形式为:

GET_LINES(变量,行数)

其中变量是一个集合类型变量,用来存放读到的几行数据。

行数也是一个变量,在读操作之前,这个参数用于指定需要读的行数,在读操作之后,这个参数表示实际读到的数据行数。
下面再通过一个例子说明读操作和写操作的综合应用。

DECLARE
data integer; --表示数据的变量
stat integer; --表示状态的变量
BEGIN
dbms_output.put(100);
dbms_output.put_line(200);
dbms_output.get_line(data,stat);
dbms_output.put_line ('缓冲区中的数据: ' || data) ;
dbms_output.put_line ('状态: '|| stat);
END;

DECLARE
data dbms_output.chararr;
stat integer; --表示状态的变量
BEGIN
dbms_output.put(100);
dbms_output.put_line(200);
stat := 1;
dbms_output.get_lines(data,stat);
for v_counter in 1..stat loop
dbms_output.put_line (data(v_counter)) ;
end loop;
END;

在上述PL/SQL块中,第一次向缓冲区中写100时使用了过程PUT ,写人数据后没有换行。
第二次向缓冲区中写200时使用了过程PUT LINE ,这样100和200被写在了同一行。

在读数据时使用了过程GET_LINE ,将刚才写人的一行数据读到变量data 中,于是变量data的值为100 200 。
而变量stat用来表示本次读操作是否成功,其值为0 ,表示读操作成功。

由此可见,在从缓冲区中读数据时,是以行为单位进行的,而不是以数据为单位。
实际上, DBMS_OUTPUT程序包本身并没有输入输出的功能,它所能做的就是对缓冲区进行读写操作。
如果使SERVEROUTPUT选项有效,则缓冲区的内容就被输出到屏幕上, PUT和PUT_LINE过程只需要把数据写入缓冲区中就可以了,这就相当于完成了输出工作
而GET_LINE的功能是从缓冲区中读一行数据,如果缓冲区中有数据,则它把当前缓冲区中最先写入的数据读出,这就相当于完成了输入工作

2. UTL_FILE程序包

UTL_FILE程序包功能是对本地操作系统的文件进行访问。

在PL/SQL块中访问文件的能力是有限的,主要包括文件的打开、关闭、读、写等操作。
在访问文件之前,必须先打开文件,这时系统将返回一个文件标识,对文件的读、写等操作都是通过这个文件标识进行的。
文件访问完后,还应该及时关闭文件。

UTL_FILE程序包中与文件的打开关闭操作有关的函数和过程有:
• FOPEN

•IS_OPEN
• FCLOSE
• FCLOSE ALL

在访问文件之前,首先要用函数FOPEN打开文件。

这个函数的调用格式为:

FOPEN(目录,文件名,打开模式)

其中目录为文件所在的位置,它与文件名一起确定了要访问的文件。

打开模式是指以什么样的方式打开文件。
UTL_FILE包规定了三种打开模式:

 r :只读方式,用于读出文件的内容。
w :写方式,用于向文件中写入数据。
a :追加方式,用于在文件末尾写入数据。

UTL_FILE程序包访问文件的功能很有限,并不是所有的文件都可以访问。

利用这个程序包只能访问指定目录中的文本文件。
为了访问某个目录中的文件,必须通过初始化参数指定这个目录,指定的格式为:
UTL_FILE_DIR=目录

例如,为了访问目录/home/oracle中的文件,需要在参数文件中添加一下内容:

UTL_FILE_DIR=/home/oracle

如果没有初始化参数指定目录,那么在访问一个目录中的文件时, Oracle将抛出一个预定义的异常UTL_FILE.INVALID PATH 。
如果要访问所有目录中的文件,可以用“*”代表任何目录,代替上面某个具体目录,但这种作法是不提倡的,因为这将带来安全隐患。
如果文件打开成功, FOPEN 函数将返回一个FILE_TYPE类型文件标识,以后对文件的访问就是通过这个文件标识进行的。
FILE_TYPE类型是在UTL_FILE包中定义的类型,用户可以直接使用。
函数IS_OPEN用于判断一个文件是否已经被打开,它只有一个参数,就是FOPEN 函数返回的文件标识。
如果文件已经被打开,函数IS_OPEN将返回真值,否则返回假值。
文件访问结束后,应该调用过程FCLOSE关闲文件。

这个过程只有一个参数,就是FOPEN函数返回的文件标识。
如果打开了多个文件,可以调用过程FCLOSE ALL关闭所有文件,这个过程没有任何参数。
与文件的读操作有关的过程为GET_LINE ,它的调用格式为:

GET_LINE(文件标识,变量)

其中文件标识就是用函数FOPEN打开文件时的返回值。

变量是一个字符串类型的变量,用于存放从文件中读到的数据。
因为对文件的读操作是以行为单位进行的,所以这个变量要能够存放文件中的一行数据。
如果一个文件是空的,或者当前已经读到了文件末尾,这时系统将抛出异常NO_DATA_FOUND 。
下面的例子演示了文件的打开、读和关闭操作。

文件打开后将它的第一行数据读出,并输出到显示器上。

DECLARE
fp UTL_FILE.FILE_TYPE;
line VARCHAR2(100);
BEGIN
fp := UTL_FILE.FOPEN('C:\Users\Administrator\Desktop', 'a.log', 'r');
UTL_FILE.GET_LINE(fp, line);
DBMS_OUTPUT.PUT_LINE(line);
UTL_FILE.FCLOSE(fp);
END;

对文件的写操作涉及的过程较多,这里仅介绍用得最多的过程PUT_LINE 。

这个过程以的形式将数据写入文件,每写入一行,就在行的末尾添加一个换行符,它的调用形式为:
PUT_LINE(文件标识,变量)

这个过程将变量中的数据写入文件标识所代表的文件中。

在下面的例子中,首先以只读方式打开第一个文件,然后以追加方式打开第二个文件。
从第一个文件中读一行数据到变量中,在这行数据的前后各添加一个“#” 后再写入第二个文件,最后关闭两个文件。

DECLARE
fp1 UTL_FILE.FILE_TYPE;
fp2 UTL_FILE.FILE_TYPE;
line VARCHAR2(100);
BEGIN
fp1 := UTL_FILE.FOPEN('C:\Users\Administrator\Desktop', 'a.log', 'r');
fp2 := UTL_FILE.FOPEN('C:\Users\Administrator\Desktop', 'a.bak', 'a');
UTL_FILE.GET_LINE(fp1, line);
UTL_FILE.PUT_LINE(fp2, '#'||line||'#');
UTL_FILE.FCLOSE_ALL;
END;

3. DBMS_SQL包

在PL/SQL块中我们可以利用SELECT命令从数据库中检索数据,也可以利用INSERT 、DELETE和UPDATE语句对数据库中的数据进行增加、删除、修改等操作。
但是像创建表、删除表、修改表结构这样的操作在PL/SQL块中是不能直接完成的,也就是说,在PL/SQL块中不能直接执行CREATE 、DROP 、ALTER这样的DDL命令。
如果要在PL/SQL块中进行这样的操作,就要借助于Oracle提供的程序包一-DBMS_SQL 。
DBMS_SQL包使得在PL/SQL包中执行DDL命令成为可能。

利用DBMS_SQL包执行DDL命令时,首先要打开一个游标,然后通过这个游标执行DDL命令,最后关闭这个游标。
DBMS_SQL包提供了一系列的过程和函数,利用这些过程和函数可以完成所需的操作。
用来打开游标的函数是OPEN_CURSOR ,这个函数没有任何参数。

如果游标打开成功,这个函数将返回一个整数,这个整数就是游标的标识。
以后执行SQL语句就是通过这个游标的标识进行的。
对SQL语句进行分析的过程是PARSE ,这个过程对SQL语句进行语法分析,将其与打开的游标、进行关联,然后执行这条SQL语句。
这个过程的调用格式为:
PARSE (游标标识, SQL语句,语言标志)
其中游标标识就是打开游标时的返回值。

SQL语句是需要执行的DDL命令的完整形式

语言标志指定该过程以什么样的方式处理SQL语句,这个参数有三个可选值:

• DBMS_SQL.V6 :采用Oracle6的方式处理SQL语句。
• DBMS_SQL.V7 :采用Oracle7的方式处理SQL语句。
• DBMS_SQL.NATIVE :采用一般方式处理SQL语句。
SQL语句执行结束后,应该及时关闭游标。

关闭游标的过程是CLOSE_CURSOR ,这个过程只有一个参数,就是通过函数OPEN_CURSOR打开的游标的标识。
例如,在下面的PL/SQL块中,首先打开一个游标,游标的标识为cur_1 ,然后利用这个游标执行一条SQL语句,创建表tl ,这个表有两个列, id和name 。
最后关闭这个游标。

这个块的执行结果是在当前用户的模式中创建了一个表t1 。

DECLARE
cur_1 integer;
str varchar2(100);
BEGIN
str := 'CREATE table t1(id integer,name varchar2(10 ))';
cur_1:=dbms_sql.open_cursor;
dbms_sql.parse(cur_1,str,DBMS_SQL.V7);
dbms_sql.close_cursor(cur_1);
END;

如果已经有一个同名的表存在,上述PL/SQL块执行时将出错。

为了向用户报告出错的情况,可以在PL/SQL块中捕捉错误,并进行异常处理,将出错的情况报告给用户,这样的PL/SQL块才算是一个完整的、健壮的程序。
下面是增加了异常处理的PL/SQL块:

DECLARE
cur_1 integer;
str varchar2(100);
already_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(already_exists,-00955);
BEGIN
str := 'CREATE table t1(id integer,name varchar2(10 ))';
cur_1:=dbms_sql.open_cursor;
dbms_sql.parse(cur_1,str,DBMS_SQL.V7);
dbms_sql.close_cursor(cur_1);
EXCEPTION
WHEN already_exists THEN
dbms_output.put_line ('需要创建的表已经存在');
END;

在这个块中定义了一个异常already_exists ,然后将它与错误号,-00955 关联起来。

错误号-0955代表的错误情况是指定的名称已经被其他对象使用。
这样当发生这个错误时,系统将抛出异常already_exists 。
在块的最后,进行的异常的处理,将错误的情况显示给用户。
实际上,在PL/SQL块中还有一种执行DDLDCL语句的方法,那就是把这样的语句作为EXECUTE IMMEDIATE命令参数
EXECUTE IMMEDIATE命令的功能是执行动态的SQL语句,它的参数可以是一个变量或一个表示SQL语句的字符串,还可以是用“||”符号连接在一起的若干字符串等。
例如:

declare
sql_stmt varchar2(100) := ' CREATE TABLE t1(id number,name char(10))';
begin
EXECUTE IMMEDIATE sql_stmt;
EXECUTE IMMEDIATE ' GRANT select,update on t1 to shu';
EXECUTE IMMEDIATE 'DROP TABLE t1';
end;

与存储程序有关的数据字典 

在数据库中,存储过程、存储函数以及程序包的信息是存放在数据字典中的。

与存储程序有关的数据字典有:

USER_PROCEURES

USER_OBJECTS

USER_SOURCE

USER_ERROR

下面是有关数据字典ALL_PROCEDURES的介绍:

ALL_PROCEDURES lists all functions and procedures that are accessible(可访问的) to the current user, along with associated properties(与之关联的属性).

For example,ALL_PROCEDURES indicates whether or not a function is pipelined, parallel enabled or an aggregate function(表明一个函数是否是一个管道类型的,可并行或者是一个聚集函数).

If a function is pipelined or an aggregate function, the associated implementation type(关联的实现类型) (if any(如果有的话)) is also identified(识别).

Related Views

  • DBA_PROCEDURES lists all functions and procedures available in the database, along with associated properties.

  • USER_PROCEDURES lists all functions and procedures owned by the current user, along with associated properties.It does not contain the OWNER column.

Column Datatype NULL Description

OWNER

VARCHAR2(128)

 

Owner of the procedure

OBJECT_NAME

VARCHAR2(128)

 

Name of the object: top-level function, procedure, or package name

PROCEDURE_NAME

VARCHAR2(128)

 

Name of the procedure

OBJECT_ID

NUMBER

 

Object number of the object

SUBPROGRAM_ID

NUMBER

 

Unique subprogram identifier(唯一子程序标识)

OVERLOAD

VARCHAR2(40)

 

Overload unique identifier

OBJECT_TYPE

VARCHAR2(13)

 

The typename of the object

AGGREGATE

VARCHAR2(3)

 

Indicates whether the procedure is an aggregate function (YES) or not (NO)

PIPELINED

VARCHAR2(3)

 

Indicates whether the procedure is a pipelined table function(管道表函数 (YES) or not (NO)

IMPLTYPEOWNER

VARCHAR2(128)

 

Owner of the implementation type(实现类型, if any

IMPLTYPENAME

VARCHAR2(128)

 

Name of the implementation type, if any

PARALLEL

VARCHAR2(3)

 

Indicates whether the procedure or function is parallel-enabled (YES) or not (NO)

INTERFACE

VARCHAR2(3)

 

YES, if the procedure/function is a table function(表函数) implemented using the ODCI interface; otherwise NO

DETERMINISTIC

VARCHAR2(3)

 

YES, if the procedure/function is declared to be deterministic(确定性); otherwise NO

AUTHID

VARCHAR2(12)

 

Indicates whether the procedure/function is declared to execute as DEFINER or CURRENT_USER (invoker)

ORIGIN_CON_ID

VARCHAR2(256)

 

The ID of the container where the data originates. Possible values include:

  • 0: This value is used for rows in non-CDBs. This value is not used for CDBs.

  • n: This value is used for rows containing data that originate in the container with container ID n (n = 1 if the row originates in root)

例如:要想查看当前用户所拥有的存储过程和存储函数,执行下面的SELECT语句:

SELECT object_name, object_type, authid FROM user_procedures;

数据字典user_objects用来存放当前用户所拥有的所有类型的数据库对象,包括表、视图、触发器、序列、存储过程、存储函数以及程序包等。

下面是有关数据字典ALL_OBJECTS的介绍:

ALL_OBJECTS describes all objects accessible to the current user.

Related Views

  • DBA_OBJECTS describes all objects in the database.

  • USER_OBJECTS describes all objects owned by the current user. This view does not display the OWNER column.

Column Datatype NULL Description

OWNER

VARCHAR2(128)

NOT NULL

Owner of the object

OBJECT_NAME

VARCHAR2(128)

NOT NULL

Name of the object

SUBOBJECT_NAME

VARCHAR2(128)

 

Name of the subobject(子对象) (for example, partition)

OBJECT_ID

NUMBER

NOT NULL

Dictionary object number of the object

DATA_OBJECT_ID

NUMBER

 

Dictionary object number of the segment that contains the object.

Note: OBJECT_ID and DATA_OBJECT_ID display data dictionary metadata(数据字典元数据). Do not confuse(迷惑) these numbers with the unique 16-byte object identifier (object ID) that Oracle Database assigns(分配) to row objects in object tables in the system.

OBJECT_TYPE

VARCHAR2(23)

 

Type of the object (such as TABLEINDEX)

CREATED

DATE

NOT NULL

Timestamp for the creation of the object

LAST_DDL_TIME

DATE

NOT NULL

Timestamp for the last modification of the object and dependent objects(依赖对象) resulting from a DDL statement (including grants and revokes)

TIMESTAMP

VARCHAR2(19)

 

Timestamp for the specification of the object (对象的时间戳格式)(character data)

STATUS

VARCHAR2(7)

 

Status of the object:

  • VALID

  • INVALID

  • N/A

如果要了解当前用户所拥有的数据库对象类型,可以执行下面的SELECT语句:
SELECT DISTINCT object_type FROM user_objects;

程序包的头部和包体的类型分别为PACKAGE和PACKAGE BODY 。
如果要查看某个数据库对象的详细信息,同样可以执行相应的SELECT语句。

例如,以下SELECT语句用来查看对象“total_income”的详细信息:

SELECT object_name, object_type, status, created FROM user_objects WHERE object_name='TOTAL_INCOME';

数据字典user_source用来存放存储过程、存储函数和程序包的源代码。

当然,这个视图的目的只是为了查看源代码, PL/SQL程序的执行并不是从这里开始的,因为程序在创建时已经经过了编译,在数据库中以二进制形式存储。

因此,试图通过修改这个数据字典而达到修改存储程序的功能是行不通的。
Oracle在创建PL/SQL程序时,将按照用户在编写时的自然格式,以行的形式存储程序代码,并记录每行的行号,所有代码行合起来就是该程序的源代码。

下面是有关数据字典ALL_SOURCE的介绍:

ALL_SOURCE describes the text source of the stored objects accessible to the current user.

Related Views

  • DBA_SOURCE describes the text source of all stored objects in the database.

  • USER_SOURCE describes the text source of the stored objects owned by the current user. This view does not display the OWNER column.

Column Datatype NULL Description
OWNER VARCHAR2(30) NOT NULL Owner of the object
NAME VARCHAR2(30) NOT NULL Name of the object
TYPE VARCHAR2(12)   Type of object: FUNCTIONJAVA SOURCEPACKAGEPACKAGE BODYPROCEDURETRIGGER,TYPETYPE BODY
LINE NUMBER NOT NULL Line number of this line of source
TEXT VARCHAR2(4000)   Text source of the stored object

例如,要查看函数tax_per_depart的源代码,可以执行下列SELECT语句:
 SELECT line, text FROM user_source WHERE name='TOTAL_INCOME';

如果在创建存储过程、存储函数或者程序包时发生了语法错误, SQL*Plus将把错误信息在屏幕上显示,同时Oraclet把错误信息记录在数据字典中
数据字典user_errors就是用来存放当前用户在创建存储程序时发生的错误的。

下面是有关数据字典ALL_ERRORS的介绍:

ALL_ERRORS describes the current errors on the stored objects accessible to the current user.

Related Views

  • DBA_ERRORS describes the current errors on all stored objects in the database.

  • USER_ERRORS describes the current errors on the stored objects owned by the current user. This view does not display the OWNER column.

Column Datatype NULL Description
OWNER VARCHAR2(30) NOT NULL Owner of the object
NAME VARCHAR2(30) NOT NULL Name of the object
TYPE VARCHAR2(12)   Type of the object:
  • VIEW

  • PROCEDURE

  • FUNCTION

  • PACKAGE

  • PACKAGE BODY

  • TRIGGER

  • TYPE

  • TYPE BODY

  • LIBRARY(库)

  • JAVA SOURCE

  • JAVA CLASS

  • DIMENSION(维度)

SEQUENCE NUMBER NOT NULL Sequence number (for ordering purposes)
LINE NUMBER NOT NULL Line number at which the error occurred
POSITION NUMBER NOT NULL Position in the line at which the error occurred
TEXT VARCHAR2(4000) NOT NULL Text of the error
ATTRIBUTE VARCHAR2(9)   Indicates whether the error is an error (ERROR) or a warning (WARNING)
MESSAGE_NUMBER NUMBER   Numeric error number (without any prefix)(数字错误号,没有任何前缀)

例如,在创建存储函数total_income时,错把SELECT
语句中的“WHERE deptno=d_no”写成了“ WHERE deptno=ddn。”,于是发生了错误:

CREATE OR REPLACE PROCEDURE total_income(d_no IN integer:=0)
AUTHID DEFINER
AS
total number;
BEGIN
if d_no=0 then --表示所有部门
SELECT sum(sal+nvl(comm, 0)) INTO total FROM emp;
else --仅表示指定的部门
SELECT sum(sal+nvl(comm, 0)) INTO total
FROM emp
WHERE deptno=ddn;
END if;
dbms_output.put_line ('总收入:'|| total);
END;

Warning: Procedure created with compilation errors

为了确定发生的所有错误的位置,执行下列查询语句:

SELECT sequence, line, position FROM user_errors;

为了查看第一个错误的详细信息,需要检索TEXT列的数据:

根据这些错误信息很快便可以确定错误的原因,从而进行纠正。

在很多情况下,发生的多个错误是由同一个原因引起的,只要修改了出现错误的程序代码,多个错误可能一起消失。
这需要用户在编写程序的过程中不断积累经验。
SQL*Plus还提供了一种查看错误信息的简便方法,用show errors命令可以查看当前发生的错误,而不需要了解数据字典的详细结构。

这个命令的用法为:

SHOW ERRORS

或者在查看错误信息时指定发生错误的对象的类型和名称,如:

SHOW ERRORS FUNCTION total_income

在这种情况下,命令show errors的格式为:

SHOW ERRORS 对象类型 对象名称

上一篇:mui的上拉加载更多 下拉刷新 自己封装的demo


下一篇:如何彻底删除TFS上的团队项目 For VS 2017