PL/SQL程序单元
本章从软件工程师角度展现PL/SQL。PL/SQL有下列程序单元:
·过程
·函数
·包说明
·包体
存储过程是作为对象存在于oracle数据库的程序单元。“存储过程”是指在数据库中已编译并可调用的程序。oracle实现存储过程作为过程、函数和包。不能调用包,可以调用包中可见部分的过程和函数。数据库厂商对存储过程有不同的实现,SQL SERVER Transact-SQL中提供了过程和函数,但没有包。
子程序一般指过程和函数。不管是一个独立的过程/函数还是包中的过程/函数.PL/SQL过程和函数模仿其他语言的过程和函数——它们接收参数,声明临时变量、使用PL/SQL编程语言结构实现一些逻辑层次。包是封装同子程序一样持久数据的程序单元。
PL/SQL是一个松散类型语言。它处理隐式类型转换。举例说,你可以传递一个number类型到一个设计接受string类型的过程中。另外,oracle在编译时和运行时都执行类型检查。如果一个过程试图给一个数量变量赋一个记录类型值在编译时类型检查将会失败。运行时错误发生在一个隐式转换失败时。number类型变量可以隐式转换为varchar2,然而,如果运行时值为9999而范围定义为varchar2(3)时,运行时约束冲突产生,因为9999对于3字节字符串来说太大了。
所有子程序参数都有一个模式,它指定被调用和调用程序的数据的方向。参数模式有:IN 、OUT、IN OUT。许多这种类型错误在编译时被发现。比如说,当一个IN模式的变量被赋值时一个编译错误会发生。当In模式的变量在赋值语句的左边时(错误)就被发觉了。
PL/SQL是一种集成了几个软件工程原则的语言:信息隐藏、封装、数据抽象、模型设计、逐步求精.语言的关键特征是打包设计语言范例。一个编译的包说明可以看作是一个详细的设计文档,使从设计到程序开发最小过渡。
另外,PL/SQL集合了ANSI SQL标准以一种方便快速开发的方式加进设计语言。这些特征的例子是select语句,使用复合记录结构和游标for循环。
这种语言的强壮特征是为java和.net开发员使用PL/SQL实现功能的一大部分提供了动机
Oracle提供的包非常广泛,下面是一些示例:
·DBMS_PIPS和DBMS_ALERT提供了分离Oracle进程异步通讯的机制,类似于UNIX命名管道。
·高级队列包括几个在分布式环境中操作逻辑队列数据的包
·DBMS_LOB包提供了处理表中字段值大到4G字节对象的支持。它允许PL/SQL过程以本地格式加载一个大文件到数据表中、比如Microsoft Word或PDF文件,
·数据库中JAVA允许一个PL/SQL包体用JAVA实现而用PL/SQL包说明作为一个PL/SQL API。举个例子就是:用PL/SQL编写包说明而用JAVA编写的包主体。
·PL/SQL程序可以使用UTL_FILE包执行IO文件操作。
·还有生成随机数、实现html服务端编程,类似于CGI模型或XML DOM API等等,
你可以从SQL*PLUS会话中执行存储过程,可以把PL/SQL代码嵌入到第三方语言中。使用Pro*C写的C程序可以包含PL/SQL块、调用存储过程;任何一个客户端接口,无论是WEB或C/S结构,都能够激活数据库中已编译的PL/SQL过程。包括ODBC、JDBC、Oracle's Net8等。
一、过程
PL/SQL过程是一个编译到oracle数据库模式的单机程序。过程可以接受参数。当编译一个过程时,CREATE PROCEDURE语句的过程标识符成为数据字典中的对象名。
PPROCEDURE procedure_name (optional parameters) IS | AS
declarative part
BEGIN
program body
EXCEPTION
exception handler
END procedure_name;
Declarative Part 是声明变量的地方,比如:
local_counter NUMBER := 0;
也可以声明复合类型结构,如records和tables
也可以声明异常,但必须在Exception部门进行处理。
Subprogram Body 包含使用PL/SQL控制结构的逻辑算法实现。PL/SQL控制结构支持loops、if-then-else、case、和声明块结构。
Exception handler 可选,类似于其他语言的try-catch模型,你可以对特定错误类型或一般异常编写处理器.
应当以动词来命名一个过程。过程常常执行一些操作如更新数据库、写数据到一个文件或者发送一条消息。
一个过程不必要有参数,如果没有参数创建过程是不需要括号。当调用一个过程时(过程没有参数)括号是可选的。
例如:
PROCEDURE insert_temp IS
BEGIN
INSERT INTO TEMP (n) VALUES (0);
END insert_temp:
调用过程:
insert_temp;
insert_temp();
都是正确的。
IS 或 AS关键字是一样的,都可以用。
PROCEDURE insert_temp IS | AS
END关键字后加过程名也是可选的,但建议加过程名。一个过程可能跨越几屏的长度,当滚屏时,有助于看到END子句知道没有跳到下一个包过程中。
END; -- acceptable.
END procedure_name; -- highly recommended in production.
完整的过程结构如下:
PROCEDURE print_temp
IS
v_average NUMBER;
v_sum NUMBER;
BEGIN
SELECT AVG(n), SUM(n) INTO v_average, v_sum
FROM TEMP;
dbms_output.put_line('Average:'||v_average);
dbms_output.put_line('Sum:'||v_sum);
END print_temp;
常常情况下要把一个单独的过程放入一个新包或一个已存在包里面。
PACKAGE temp_operations IS
PROCEDURE insert_temp;
END temp_operations;
PACKAGE BODY temp_operations IS
PROCEDURE insert_temp IS
BEGIN
INSERT INTO temp (n) VALUES (0);
END insert_temp;
END temp_operations;
使用下面方式调用:
temp_operations.insert_temp;
temp_operations.insert_temp();
二、函数
一些语言有单一的子程序类型,java类中有方法,方法可以返回值也可以不返回值。C语言也是,但C语言中仅有函数。一个C函数可以返回一个值或修改作为参数传递进函数内的地址的内容。
PL/SQL包括两种子程序:过程和函数,包可以看作是一些对象操作的封装。对象可以是表、数据管道、文件、远程数据表、或者其他对象。过程在对象上执行操作并更改对象。函数提供了获取对象状态和情形信息的手段。
FUNCTION student_status(optional parameters)
RETURN VARCHAR2 IS
declarative part
BEGIN
program body
RETURN expression;
EXCEPTION
exception handler code
that should include a RETURN
END student_student_status;
参数是可选的,但RETURN语句是必须的。FUNCTION必须有一个return 语句。
下面的例子是返回一个DATE类型的函数:
CREATE OR REPLACE FUNCTION tomorrow RETURN DATE
IS
next_day DATE;
BEGIN
next_day := SYSDATE + 1;
RETURN next_day;
END tomorrow;
函数中的return语句可以是一个表达式,上面函数可以直接返回一个表达式而不用本地变量:
FUNCTION tomorrow RETURN DATE IS
BEGIN
RETURN SYSDATE + 1;
END tomorrow;
函数于过程结果相似,只是函数返回值,而过程不返回值
三、包说明
设计常常引入程序相关的“包装”。这包括编译一组程序到一个对象库,在一个源码目录下配置一个单一子程序代码。管理配置工具允许我们在一个逻辑和功能性主题区域包装我们的软件,以便开发员为了Check-out和Check-in来定位程序。
一些环境中,软件单元的最高抽象是过程。使软件库管理变成许多单一的程序管理。
包说明是一个单独的ASCII文件,作为一个独立的程序单元编译。包主体也是一个单独的ASCII文件,包体仅在包说明编译成功后编译。也可以把包说明和包体放在一个文件中。
1、语法和风格
包说明基本语法:
CREATE PACKAGE package_name IS
Type definitions for records, index-by tables,
varrays, nested tables
Constants
Exceptions
Global variable declarations
PROCEDURE procedure_name_1 (parameters & types);
PROCEDURE procedure_name_2 (parameter & types);
FUNCTION function_name_1 (parameters & types) RETURN type;
END package_name;
包说明中过程和函数没有顺序限制。
包主体将包含包说明中每一个子程序的PL/SQL代码。包说明中的每一个子程序包体中必须有相应的子程序体。
在包说明中的数据对象声明是全局的。因此,仅声明需要全局定义的对象。
在包体内的过程语句,包括子程序名、参数名、参数模式、参数类型,必须匹配包说明中的过程语句。同样,函数也是一样。
包主体模板如下:
CREATE PACKAGE BODY package_name IS
PROCEDURE procedure_name_1 (parameters & types)
IS
local variables
BEGIN
body of code
END procedure_name_1;
PROCEDURE procedure_name_2 (parameter & types)
IS
local variables
BEGIN
body_of_code
END procedure_name_2;
FUNCTION function_name_1 (parameters & types) RETURN type
IS
local variables
BEGIN
body of code
RETURN statement;
END function_name_1;
END package_name;
2、示例:
CREATE OR REPLACE PACKAGE students_pkg IS
PROCEDURE add_student
(v_student_name IN students.student_name%TYPE,
v_college_major IN students.college_major%TYPE,
v_status IN students.status%TYPE,
v_state IN students.state%TYPE DEFAULT NULL,
v_license_no IN students.license_no%TYPE DEFAULT NULL);
FUNCTION NO_OF_STUDENTS
(v_major IN major_lookup.major_desc%TYPE DEFAULT NULL,
v_status IN students.status%TYPE DEFAULT NULL)
RETURN NUMBER;
END students_pkg;
四、包主体
下面是上例的包主体实现:
CREATE OR REPLACE PACKAGE BODY students_pkg IS
PROCEDURE add_student
(v_student_name IN students.student_name%TYPE,
v_college_major IN students.college_major%TYPE,
v_status IN students.status%TYPE,
v_state IN students.state%TYPE DEFAULT NULL,
v_license_no IN students.license_no%TYPE DEFAULT NULL)
IS
BEGIN
INSERT INTO students VALUES
('A'||students_pk_seq.NEXTVAL,
v_student_name,
v_college_major,
v_status,
v_state,
v_license_no);
END add_student;
FUNCTION NO_OF_STUDENTS
(v_major IN major_lookup.major_desc%TYPE DEFAULT NULL,
v_status IN students.status%TYPE DEFAULT NULL)
RETURN NUMBER
IS
ccount INTEGER;
BEGIN
SELECT COUNT (*) INTO ccount
FROM students, major_lookup
WHERE students.college_major = major_lookup.major
AND major_lookup.major_desc =
nvl(v_major,major_lookup.major_desc)
AND students.status = nvl(v_status,students.status);
RETURN ccount;
END NO_OF_STUDENTS;
END students_pkg;
开发包主体可能需要其他本地过程和函数,这些是隐藏的,叫私有(过程或函数)。
五、参数和模式
PL/SQL程序有3种模式:
·IN (default)
·IN OUT
·OUT
(1)、IN 模式参数是一个常量
IN模式参数是一个常量必须被看作常量。下面的过程将不能编译成功以为第3行是一个IN模式变量
1 PROCEDURE print_next_value(v_data IN INTEGER) IS
2 BEGIN
3 v_data := v_data+1; -- compile error
4 dbms_output.put_line(v_data);
5 END;
常量可以用在表达式中,下面的用法是正确的:
1 PROCEDURE print_next_value(v_data IN INTEGER) IS
2 BEGIN
3 dbms_output.put_line(v_data+1);
4 END;
(2)、IN OUT模式
IN OUT模式的变量既可以在赋值语句的左边,也可以在赋值语句的右边。
1 PROCEDURE change_data(v_data IN OUT INTEGER) IS
2 BEGIN
3 for i in 1..10 loop
4 v_data := v_data + 1;
5 end loop;
6 END;
(3)、OUT模式
在下例中,第4行之前,v_data变量是一个null,在使用OUT模式变量前必须先给他赋一个值:
1 PROCEDURE provide_data(v_data OUT INTEGER)
2 IS
3 BEGIN
4 v_data := 100;
5 FOR i IN 1..10 LOOP
6 v_data := v_data +1;
7 END LOOP;
8 END;
(4)、参数默认值
过程或函数说明可以为IN或IN OUT参数定义一个初始默认值。下面两种方法都是正确的:
PROCEDURE name
(argument mode datatype := a_default_value);
PROCEDURE name
(argument mode datatype DEFAULT a_default_value);
下面函数定义了以默认半径为1返回圆的面积:
FUNCTION circle
(radius IN NUMBER := 1) RETURN NUMBER IS
BEGIN
RETURN 3.14 * radius**2;
END;
FUNCTION circle
(radius IN NUMBER DEFAULT 1) RETURN NUMBER IS
BEGIN
RETURN 3.14 * radius**2;
END;
(5)、%TYPE
%TYPE 的意思是变量声明类型和数据库表的指定字段类型一致。
variable_name table_name.column_name%TYPE;
CREATE OR REPLACE PROCEDURE get_professor_salary
(v_prof_name IN professors.prof_name%TYPE,
v_salary OUT professors.salary%TYPE);