第二部分 PL/SQL编程
第六章 函数和存储
三种参数模式:只读(IN)、只写(OUT)、读写(IN OUT)
默认为只读(IN)模式.
注:你不能在一个查询中调用包含DML操作的函数.
在函数里可使用DETERMINISTIC或PARALLEL_ENABLED、PIPELINED、RESULT_CACHE等子句.
DETERMINISTIC用来限制在包里的函数能做的事情.
它确保函数对于任何输入总是工作在同样的方式.前提是函数不能读或写像包、数据表等外部源.
常用在物化视图和函数索引.
它的几种选项:
RNDS--确保函数处于没有读任何数据的状态.也就是你不能在函数中使用SQL查询或任何类型.
如果你违反了,将会得到错误PLS-00452.
WNDS--确保函数处于不能写任何数据的状态.也就是不能在函数中有增删改的语句.
如果你违反了,将会得到错误PLS-00452.
RNPS--确保函数处于不能读任何包的状态.也就是不能访问任何包变量.
如果你违反了,将会得到错误PLS-00452.
WNPS--确保函数处于不能写任何包的状态.也就是不能写任何值到包变量里,也就是不能改变包变量.
如果你违反了,将会得到错误PLS-00452.
TRUST--命令函数不去检查是否调用了RESTRICT_REFERENCES选项.(也就是以上的各个选项)
如:
--DETERMINISTIC保证对于任何输入总是工作在同样的方式,也就是同样的输入一定会有同样的输出
--用来防止递归调用函数,如前一次调用后,再次调用该函数,同样的输入可直接读取前一次的结果集
CREATE OR REPLACE FUNCTION PV(FUTURE_VALUE NUMBER,
PERIODS NUMBER,
INTEREST NUMBER) RETURN NUMBER
DETERMINISTIC IS
BEGIN
RETURN FUTURE_VALUE /((1 + INTEREST) ** PERIODS);
END PV;
PARALLEL_ENABLED是启用并行.
CREATE OR REPLACE FUNCTION MERGE(LAST_NAME VARCHAR2,
FIRST_NAME VARCHAR2,
MIDDLE_INITIAL VARCHAR2) RETURN VARCHAR2
PARALLEL_ENABLE IS
BEGIN
RETURN LAST_NAME || ', ' || FIRST_NAME || ' ' || MIDDLE_INITIAL;
END;
PIPELINED让你的函数返回一个管道表.(不能做为参数类型使用)
例:
00:03:37 hr@orcl> CREATE OR REPLACE TYPE numbers AS VARRAY(10) OF NUMBER; 00:03:43 2 / 类型已创建。 已用时间: 00: 00: 00.20 00:03:44 hr@orcl> CREATE OR REPLACE FUNCTION PIPELINED_NUMBERS RETURN NUMBERS 00:03:49 2 PIPELINED IS 00:03:49 3 LIST NUMBERS := NUMBERS(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 00:03:49 4 BEGIN 00:03:49 5 FOR I IN 1 .. LIST.LAST LOOP 00:03:49 6 PIPE ROW(LIST(I)); 00:03:49 7 END LOOP; 00:03:49 8 RETURN; 00:03:49 9 END; 00:03:49 10 / 函数已创建。 已用时间: 00: 00: 00.09 00:03:51 hr@orcl> SELECT * FROM TABLE(pipelined_numbers); COLUMN_VALUE ------------ 0 1 2 3 4 5 6 7 8 9 已选择10行。
RESULT_CACHE可以让你使用跨会话的结果集缓存功能
CREATE OR REPLACE TYPE strings AS TABLE OF VARCHAR2(60);
/
CREATE OR REPLACE FUNCTION GET_TITLE(PARTIAL_TITLE VARCHAR2) RETURN STRINGS
RESULT_CACHE RELIES_ON(ITEM) IS--启用结果集缓存,RELIES_ON保证结果集的完整性
-- Declare a collection control variable and collection variable.
COUNTER NUMBER := 1;
RETURN_VALUE STRINGS := STRINGS();
CURSOR GET_TITLE(PARTIAL_TITLE VARCHAR2) IS
SELECT ITEM_TITLE
FROM ITEM
WHERE UPPER(ITEM_TITLE) LIKE '%' || UPPER(PARTIAL_TITLE) || '%';
BEGIN
-- Read the data and write it to the collection in a cursor FOR loop.
FOR I IN GET_TITLE(PARTIAL_TITLE) LOOP
RETURN_VALUE.EXTEND;
RETURN_VALUE(COUNTER) := I.ITEM_TITLE;
COUNTER := COUNTER + 1;
END LOOP;
RETURN RETURN_VALUE;
END GET_TITLE;
/
--使用
DECLARE
LIST STRINGS;
BEGIN
LIST := GET_TITLE('Harry');
FOR I IN 1 .. LIST.LAST LOOP
DBMS_OUTPUT.PUT_LINE('list(' || I || ') : [' || LIST(I) || ']');
END LOOP;
END;
结果集缓存使用限制:
1.不能定义在使用调用者权限的模块里或者匿名模块里
2.不能在管道函数中使用
3.没有传引用的语法,也就是不能使用IN OUT、OUT.
4.参数不能使用BLOB, CLOB, NCLOB, REF CURSOR,集合,对象,记录等类型.
5.不能返回使用第4点中类型的变量.
--返回游标的函数(使用系统游标SYS_REFCURSOR)
CREATE OR REPLACE FUNCTION GET_FULL_TITLES RETURN SYS_REFCURSOR IS
TITLES SYS_REFCURSOR;
BEGIN
OPEN TITLES FOR
SELECT ITEM_TITLE, ITEM_SUBTITLE FROM ITEM;
RETURN TITLES;
END;
--函数中使用自治事务处理DML语句
CREATE OR REPLACE FUNCTION ADD_USER(SYSTEM_USER_ID NUMBER,
SYSTEM_USER_NAME VARCHAR2,
SYSTEM_GROUP_ID NUMBER,
SYSTEM_USER_TYPE NUMBER,
LAST_NAME VARCHAR2,
FIRST_NAME VARCHAR2,
MIDDLE_INITIAL VARCHAR2,
CREATED_BY NUMBER,
CREATION_DATE DATE,
LAST_UPDATED_BY NUMBER,
LAST_UPDATE_DATE DATE) RETURN BOOLEAN IS
-- Set function to perform in its own transaction scope.
PRAGMA AUTONOMOUS_TRANSACTION;
-- Set default return value.
RETVAL BOOLEAN := FALSE;
BEGIN
INSERT INTO SYSTEM_USER
VALUES
(SYSTEM_USER_ID,
SYSTEM_USER_NAME,
SYSTEM_GROUP_ID,
SYSTEM_USER_TYPE,
LAST_NAME,
FIRST_NAME,
MIDDLE_INITIAL,
CREATED_BY,
CREATION_DATE,
LAST_UPDATED_BY,
LAST_UPDATE_DATE);
-- Save change inside its own transaction scope.
COMMIT;
-- Reset return value.
RETVAL := TRUE;
RETURN RETVAL;
END;
递归函数
递归函数在解决某些复杂问题(如高级解析)时是很有用的.
它是一种按指定形式传值的函数.(注意递归函数的参数不能是OUT或IN OUT模式的,它是传值的不是传引用的)
阶乘问题是一种很常见的线性递归问题.
--递归函数实现阶乘 22:10:11 hr@orcl> CREATE OR REPLACE FUNCTION FACTORIAL(N BINARY_DOUBLE) RETURN BINARY_DOUBLE IS 22:10:11 2 BEGIN 22:10:11 3 IF N <= 1 THEN 22:10:11 4 RETURN 1; --基础语句 22:10:11 5 ELSE 22:10:11 6 RETURN N * FACTORIAL(N - 1);--递归语句 22:10:11 7 END IF; 22:10:11 8 END FACTORIAL; 22:10:15 9 / 函数已创建。 已用时间: 00: 00: 00.07 22:10:17 hr@orcl> select FACTORIAL(6) from dual; FACTORIAL(6) ------------ 7.2E+002 已选择 1 行。 已用时间: 00: 00: 00.04 22:10:34 hr@orcl> col a format 999999 22:10:52 hr@orcl> select FACTORIAL(6) a from dual; A ------- 720 已选择 1 行。
--递归实现斐波拉契数列(非线性递归) 22:14:32 hr@orcl> CREATE OR REPLACE FUNCTION FIBONACCI(N BINARY_DOUBLE) RETURN BINARY_DOUBLE IS 22:14:33 2 BEGIN 22:14:33 3 IF N <= 2 THEN 22:14:33 4 RETURN 1; 22:14:33 5 ELSE 22:14:33 6 RETURN FIBONACCI(N - 2) + FIBONACCI(N - 1); 22:14:33 7 END IF; 22:14:33 8 END FIBONACCI; 22:14:34 9 / 函数已创建。 已用时间: 00: 00: 00.09 22:14:47 hr@orcl> select FIBONACCI(10) a from dual; A ------- 55 已选择 1 行。
传引用的函数(带OUT或IN OUT模式的参数的函数)
传引用的函数接受的参数是参数值的副本,除非你指定使用NOCOPY模式.NOCOPY仅仅在某些函数上需要.
SQL*PLUS里可以定义会话级变量(也就是绑定变量).调用函数时,你不能向OUT或IN OUT模式的参数值赋常量值.
传引用函数的规则:
1.必须有一个参数定义为OUT或IN OUT模式
2.所有参数有其局部作用域,你可以更改它们
3.任何参数可以使用有效的SQL或PL/SQL数据类型.只有参数列表使用SQL数据类型
4.任何IN模式的参数可以有一个缺省的初始值
5.正常函数返回可以使用任何SQL或PL/SQL数据类型,但是管道函数必须返回一个表.
6.在函数里任何从查询转换来的系统引用游标(OPEN 游标 FOR 查询)是不可写的,因此该游标传递给另外的
函数或存储时只能以IN模式传递.
--传引用的函数,注意这里假定输入参数大于0且不超过5 00:11:57 hr@orcl> CREATE OR REPLACE FUNCTION COUNTING(NUMBER_IN IN OUT NUMBER) 00:11:58 2 RETURN VARCHAR2 IS 00:11:58 3 -- Declare a collection control variable and collection variable 00:11:58 4 TYPE NUMBERS IS TABLE OF VARCHAR2(5); 00:11:58 5 ORDINAL NUMBERS := NUMBERS('One', 'Two', 'Three', 'Four', 'Five'); 00:11:58 6 -- Define default return value. 00:11:58 7 RETVAL VARCHAR2(9) := 'Not Found'; 00:11:58 8 BEGIN 00:11:58 9 -- Replace a null value to ensure increment. 00:11:58 10 IF NUMBER_IN IS NULL THEN 00:11:58 11 NUMBER_IN := 1; 00:11:58 12 END IF; 00:11:58 13 -- Increment actual parameter when within range. 00:11:58 14 IF NUMBER_IN < 4 THEN 00:11:58 15 RETVAL := ORDINAL(NUMBER_IN); 00:11:58 16 NUMBER_IN := NUMBER_IN + 1; 00:11:58 17 ELSE 00:11:58 18 RETVAL := ORDINAL(NUMBER_IN); 00:11:58 19 END IF; 00:11:58 20 RETURN RETVAL; 00:11:58 21 END; 00:11:59 22 / 函数已创建。 --调用该函数 00:12:00 hr@orcl> DECLARE 00:14:08 2 I INTEGER := 2; 00:14:08 3 V INTEGER; 00:14:08 4 BEGIN 00:14:08 5 DBMS_OUTPUT.PUT_LINE(COUNTING(I)); 00:14:08 6 END; 00:14:09 7 / Two PL/SQL 过程已成功完成。
--参数OUT模式的情况,此时函数参数未返回前总是为NULL 00:19:13 hr@orcl> CREATE OR REPLACE FUNCTION COUNTING(NUMBER_OUT OUT NUMBER) RETURN VARCHAR2 IS 00:19:15 2 TYPE NUMBERS IS TABLE OF VARCHAR2(5); 00:19:15 3 ORDINAL NUMBERS := NUMBERS('One', 'Two', 'Three', 'Four', 'Five'); 00:19:15 4 RETVAL VARCHAR2(9) := 'Not Found'; 00:19:15 5 BEGIN 00:19:15 6 -- Replace a null value to ensure increment. 00:19:15 7 IF NUMBER_OUT IS NULL THEN 00:19:15 8 NUMBER_OUT := 1; 00:19:15 9 END IF; 00:19:15 10 -- Increment actual parameter when within range. 00:19:15 11 IF NUMBER_OUT < 4 THEN 00:19:15 12 RETVAL := ORDINAL(NUMBER_OUT); 00:19:15 13 NUMBER_OUT := NUMBER_OUT + 1; 00:19:15 14 ELSE 00:19:15 15 RETVAL := ORDINAL(NUMBER_OUT); -- Never run because number_out is always null. 00:19:15 16 END IF; 00:19:15 17 RETURN RETVAL; 00:19:15 18 END; 00:19:16 19 / 函数已创建。 00:19:18 hr@orcl> DECLARE 00:20:23 2 COUNTER NUMBER := 1; 00:20:23 3 BEGIN 00:20:23 4 FOR I IN 1 .. 5 LOOP 00:20:23 5 DBMS_OUTPUT.PUT('Counter [' || COUNTER || ']'); 00:20:23 6 DBMS_OUTPUT.PUT_LINE('[' || COUNTING(COUNTER) || ']'); 00:20:23 7 END LOOP; 00:20:23 8 END; 00:20:24 9 / Counter [1][One] Counter [2][One] Counter [2][One] Counter [2][One] Counter [2][One] PL/SQL 过程已成功完成。
存储过程
存储过程不能作为右操作数或用在SQL语句中,它也支持IN,OUT和IN OUT模式.
像函数一样,存储也可以嵌套存储,也可以在执行块嵌套匿名块.
原型:
PROCEDURE procedure_name
[( parameter1 [IN][OUT] [NOCOPY] sql_datatype | plsql_datatype
, parameter2 [IN][OUT] [NOCOPY] sql_datatype | plsql_datatype
, parameter(n+1) [IN][OUT] [NOCOPY] sql_datatype | plsql_datatype )]
[ AUTHID DEFINER | CURRENT_USER ] IS
declaration_statements
BEGIN
execution_statements
[EXCEPTION]
exception_handling_statements
END [procedure_name];
存储可以有或者没有参数.
和函数一样,存储也有传值和传引用的参数.
AUTHID子句设置存储的调用权限模式,默认是定义者权限.
定义者权限是指任何有执行该存储的权限的用户视为拥有同样的模式(SCHEMA).
CURRENT_USER设置为调用者权限,是指你调用存储在你的本地模式一样,那么要求你拥有存储所依赖的对象的调用权限,否则无法调用该存储.
存储经常用来执行各自DML操作以及事务管理.
使用PRAGMA AUTONOMOUS_TRANSACTION来使得存储运行在一个单独的事务里面.
传值存储
传值存储调用时接受一个传入变量的副本值.这种存储不需要返回一个变量.
传值存储的5个规则:
1.所有的形参必须定义为IN模式
2.所有的形参是局部作用域变量,值不能被改变.
3.任何形参可以使用有效的SQL/PLSQL数据类型
4.任何形参可以有一个缺省的初始值
5.从函数里转换来的系统引用游标(OPEN 游标 FOR 查询)是不可写的,因此该游标传递给另外的
函数或存储时只能以IN模式传递.游标变量实际是一个引用或句柄,它指向内部的只读结构的结果缓存集.
--一个传值存储的例子,用来向表插入数据
CREATE OR REPLACE PROCEDURE ADD_CONTACT(MEMBER_ID NUMBER,
CONTACT_TYPE NUMBER,
LAST_NAME VARCHAR2,
FIRST_NAME VARCHAR2,
MIDDLE_INITIAL VARCHAR2 := NULL,
ADDRESS_TYPE NUMBER := NULL,
STREET_ADDRESS VARCHAR2 := NULL,
CITY VARCHAR2 := NULL,
STATE_PROVINCE VARCHAR2 := NULL,
POSTAL_CODE VARCHAR2 := NULL,
CREATED_BY NUMBER,
CREATION_DATE DATE := SYSDATE,
LAST_UPDATED_BY NUMBER,
LAST_UPDATE_DATE DATE := SYSDATE) IS
-- Declare surrogate key variables.
CONTACT_ID NUMBER;
ADDRESS_ID NUMBER;
STREET_ADDRESS_ID NUMBER;
-- Define autonomous function to secure any surrogate key values.
FUNCTION GET_SEQUENCE_VALUE(SEQUENCE_NAME VARCHAR2) RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;
ID_VALUE NUMBER;
STATEMENT VARCHAR2(2000);
BEGIN
-- Build and run dynamic SQL in a PL/SQL block.
STATEMENT := 'BEGIN' || CHR(10) || ' SELECT ' || SEQUENCE_NAME ||
'.nextval' || CHR(10) || ' INTO :id_value' || CHR(10) ||
' FROM dual;' || CHR(10) || 'END;';
EXECUTE IMMEDIATE STATEMENT
USING OUT ID_VALUE;
RETURN ID_VALUE;
END GET_SEQUENCE_VALUE;
BEGIN
-- Set savepoint to guarantee all or nothing happens.
SAVEPOINT ADD_CONTACT;
-- Assign next value from sequence and insert record.
CONTACT_ID := GET_SEQUENCE_VALUE('CONTACT_S1');
INSERT INTO CONTACT
VALUES
(CONTACT_ID,
MEMBER_ID,
CONTACT_TYPE,
LAST_NAME,
FIRST_NAME,
MIDDLE_INITIAL,
CREATED_BY,
CREATION_DATE,
LAST_UPDATED_BY,
LAST_UPDATE_DATE);
-- Check before inserting data in ADDRESS table.
IF ADDRESS_TYPE IS NOT NULL AND CITY IS NOT NULL AND
STATE_PROVINCE IS NOT NULL AND POSTAL_CODE IS NOT NULL THEN
-- Assign next value from sequence and insert record.
ADDRESS_ID := GET_SEQUENCE_VALUE('ADDRESS_S1');
INSERT INTO ADDRESS
VALUES
(ADDRESS_ID,
CONTACT_ID,
ADDRESS_TYPE,
CITY,
STATE_PROVINCE,
POSTAL_CODE,
CREATED_BY,
CREATION_DATE,
LAST_UPDATED_BY,
LAST_UPDATE_DATE);
-- Check before inserting data in STREET_ADDRESS table.
IF STREET_ADDRESS IS NOT NULL THEN
-- Assign next value from sequence and insert record.
STREET_ADDRESS_ID := GET_SEQUENCE_VALUE('STREET_ADDRESS_S1');
INSERT INTO STREET_ADDRESS
VALUES
(STREET_ADDRESS_ID,
ADDRESS_ID,
STREET_ADDRESS,
CREATED_BY,
CREATION_DATE,
LAST_UPDATED_BY,
LAST_UPDATE_DATE);
END IF;
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO ADD_CONTACT;
RAISE_APPLICATION_ERROR(-20001, SQLERRM);
END ADD_CONTACT;
/
传引用存储
传引用存储在调用时候获取传入变量的引用值.
这种类型的存储可以改变实参的值.
规则类似函数的传引用。
例:
--使用NDS(本地动态SQL)的函数,先构造函数,后面内联调用该函数
CREATE OR REPLACE FUNCTION GET_SEQUENCE_VALUE(SEQUENCE_NAME VARCHAR2)
RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;--自治事务
ID_VALUE NUMBER;
STATEMENT VARCHAR2(2000);
BEGIN
-- Build dynamic SQL statement as anonymous block PL/SQL unit.
STATEMENT := 'BEGIN' || CHR(10) || ' SELECT ' || SEQUENCE_NAME ||
'.nextval' || CHR(10) || ' INTO :id_value' || CHR(10) ||
' FROM dual;' || CHR(10) || 'END;';
-- Execute dynamic SQL statement.
EXECUTE IMMEDIATE STATEMENT
USING OUT ID_VALUE;
RETURN ID_VALUE;
END GET_SEQUENCE_VALUE;
/
CREATE OR REPLACE PROCEDURE ADD_CONTACT(CONTACT_ID OUT NUMBER, -- Primary key after insert.
MEMBER_ID IN NUMBER, -- Foreign key preceding insert.
CONTACT_TYPE IN NUMBER,
LAST_NAME IN VARCHAR2,
FIRST_NAME IN VARCHAR2,
MIDDLE_INITIAL IN VARCHAR2 := NULL,
CREATED_BY IN NUMBER,
CREATION_DATE IN DATE := SYSDATE,
LAST_UPDATED_BY IN NUMBER,
LAST_UPDATE_DATE IN DATE := SYSDATE) IS
BEGIN
-- Set savepoint so that all or nothing happens.
SAVEPOINT ADD_CONTACT;
-- Suggest inlining the get_sequence_value function.
PRAGMA INLINE(GET_SEQUENCE_VALUE, 'YES');--内联调用函数GET_SEQUENCE_VALUE
-- Assign next value from sequence and insert record.
CONTACT_ID := GET_SEQUENCE_VALUE('CONTACT_S1');
INSERT INTO CONTACT
VALUES
(CONTACT_ID,
MEMBER_ID,
CONTACT_TYPE,
LAST_NAME,
FIRST_NAME,
MIDDLE_INITIAL,
CREATED_BY,
CREATION_DATE,
LAST_UPDATED_BY,
LAST_UPDATE_DATE);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO ADD_CONTACT;
RAISE_APPLICATION_ERROR(-20001, SQLERRM);
END ADD_CONTACT;
/