Monday, March 9, 2009

invoke the cursor valued stored function/procedure

这节主要讨论, 如何在PL/SQL中怎样调用另一个存储函数或存储过程呢?
如果一个函数返回的是scalar值, 那么调用这个自定义的函数和调用内建的函数是完全一样的.

如果你的自定义的函数返回的是一个游标, 调用这个函数本身并不复杂, 但是怎样循环这个游标却比较诡异. 如下面的例子, FUN_GET_JOB_INFO返回的游标不是直接来源于个表, 而是动态生成的一个recordset, 你不能将这个游标函数看作一个普通表一样,放在from从句中, 只能像普通游标一样使用它, 比如用Loop语句来遍历这个游标. 因为调用这个游标函数, 获得的其实就是一个游标.

Fetch这个cursor的row时候, 不能将它直接将整个row放到你自定义的row 类型变量中, 这会引起convert错误. 你应当将这个row分解成若干个字段, 将row放到这些字段中.
比如
FETCH CURSOR1 INTO V_JOB_CODE, V_JOB_NAME;




--create one object type outside of one package
create or replace type TYPE_JOB_ROW as object
(
JOB_CODE VARCHAR(50),
JOB_NAME VARCHAR(200)
);

--create one table type outside of one package
CREATE OR REPLACE TYPE TYPE_JOB_TABLE is TABLE OF TYPE_JOB_ROW;


包的代码是:


create or replace package PKG_TEST4 is

type type_result_table is ref cursor ;

function FUN_GET_JOB_INFO RETURN type_result_table ;
function FUN_GET_ONE_JOB(v_arg varchar2, v_Arg_Is_JOB_Code in char) return varchar2;

end PKG_TEST4;
/
create or replace package body PKG_TEST4 is

/*
Purpose: To fetch ALL JOB code or name
*/
function FUN_GET_JOB_INFO RETURN type_result_table
IS
result_table type_result_table ;
JOB_TABLE TYPE_JOB_TABLE:=TYPE_JOB_TABLE() ;
JOB_ROW TYPE_JOB_ROW ;
BEGIN
JOB_ROW:=TYPE_JOB_ROW(JOB_CODE => 'A',JOB_NAME => 'A123') ;
JOB_ROW.JOB_NAME:='A12' ; --Change the job_name from A123->A12
JOB_TABLE.extend();
JOB_TABLE(JOB_TABLE.count) := JOB_ROW ;

OPEN result_table FOR
SELECT * FROM table(JOB_TABLE) ;

return(result_table);
END ;


/*
Purpose: To fetch on specific JOB code or name
*/
function FUN_GET_ONE_JOB(v_arg varchar2, v_Arg_Is_JOB_Code in char)
return varchar2 is
result varchar2(50);
cursor1 type_result_table;
V_JOB_CODE VARCHAR2(20);
V_JOB_NAME VARCHAR2(20);
V_JOB_ROW TYPE_JOB_ROW;
begin
result := '';
--call the cursor valued function
cursor1 := FUN_GET_JOB_INFO;
LOOP
--Fetch the record into 2 variables
FETCH CURSOR1 INTO V_JOB_CODE, V_JOB_NAME;

--the following code is wrong, because it will fail to convert the row type.
--FETCH CURSOR1 INTO V_JOB_ROW;

if (v_Arg_Is_JOB_Code = 'Y') or (v_Arg_Is_JOB_Code = 'y') then
if (V_JOB_CODE = v_arg) then
result := V_JOB_name;
end if;
else
if (V_JOB_name = v_arg) then
result := V_JOB_code;
end if;
end if;
EXIT WHEN CURSOR1%NOTFOUND;

END LOOP;
return(Result);
end;

end PKG_TEST4;
/

Wednesday, February 25, 2009

Using PL/SQL Table variable

在SQL Server的存储过程中, 可以非常方便地使用临时表#TempTable, 你可以随意地将数据插入到临时表, 可以将临时表的记录作为存储过程的结果集. #TempTable用过后, 它就干干净净地消失了, 包括表的structure.
而Oracle的临时表是global temporary table, 这表明它的定义是persistent, 只是内容是临时的.
那么在Oracle中如何才能做到像SQL Server一样的临时表呢? Oracle为我们提供了PL/SQL Table表变量, 其实就是内存表.


create or replace package PKG_TEST2 is

TYPE TYPE_NESTED_JOB_ROW IS RECORD
(JOB_CODE VARCHAR(50),
JOB_NAME VARCHAR(200));

TYPE TYPE_NESTED_JOB_TABLE is TABLE OF TYPE_NESTED_JOB_ROW ;

type type_result_table is ref cursor ;

procedure SP_GET_JOB_INFO(result_table out type_result_table) ;

end PKG_TEST2;
/
create or replace package body PKG_TEST2 is

procedure SP_GET_JOB_INFO(result_table out type_result_table)
IS
JOB_TABLE TYPE_NESTED_JOB_TABLE:=TYPE_NESTED_JOB_TABLE() ;
JOB_ROW TYPE_NESTED_JOB_ROW ;
BEGIN
JOB_ROW.JOB_CODE:='A' ;
JOB_ROW.JOB_NAME:='A12123' ;
JOB_TABLE.extend();
JOB_TABLE(JOB_TABLE.count) := JOB_ROW ;

JOB_ROW.JOB_CODE:='B' ;
JOB_ROW.JOB_NAME:='B12123' ;
JOB_TABLE.extend();
JOB_TABLE(JOB_TABLE.count) := JOB_ROW ;

DBMS_OUTPUT.put_line(JOB_TABLE.COUNT) ;
--OUTPUT RESULT IS 2

--The following code can not compile succeeded
--OPEN result_table for
-- select * from table(JOB_ROWS) ;
END ;

procedure SP_GET_JOB_INFO_2(IN_Job_ID in number, result_table out type_result_table)
IS
JOB_TABLE TYPE_NESTED_JOB_TABLE:=TYPE_NESTED_JOB_TABLE() ;
JOB_ROW TYPE_NESTED_JOB_ROW ;

cursor CURSOR_JOBS is
SELECT * FROM EMPLOYEE_JOBS
WHERE JOB_ID>=IN_Job_ID;
BEGIN
for r_job in CURSOR_JOBS
loop
JOB_ROW.JOB_CODE:=r_job.JOB_CODE;
JOB_ROW.JOB_NAME:=r_job.JOB_NAME;
JOB_TABLE.extend();
JOB_TABLE(JOB_TABLE.count) := JOB_ROW ;
end loop;


DBMS_OUTPUT.put_line(JOB_TABLE.COUNT) ;
--OUTPUT RESULT IS 2

--The following code can not compile succeeded
--OPEN result_table for
-- select * from table(JOB_ROWS) ;
END ;

end PKG_TEST2;
/

在上面的例子中, 我们在package中声明了一个record类型(TYPE_NESTED_JOB_ROW)和一个table类型(TYPE_NESTED_JOB_TABLE), 在存储过程SP_GET_JOB_INFO中, 可以实例化这个table类型变量JOB_TABLE, 你可以向JOB_TABLE插入单个的记录. 如果使用Loop语句的话, 你可以批量地插入许多记录到这个表变量中, 就像存储过程SP_GET_JOB_INFO2.

这种在package中定义的table类型变量, 对于一般的DML操作是非常实用的, 但如果你要将table表变量的结果作为ref cursor类型的存储过程out参数暴露出来, 就不行了. 在上面的示例代码中, 如果将取消最后两句代码的注释, 你就发现这个存储过程不能被编译. 为什么呢? 详见http://forums.oracle.com/forums/thread.jspa?threadID=623197

简单讲, 原因就是, 我们是在package中定义的TYPE_NESTED_JOB_TABLE的table类型, 这个类型对于该package之外其实是不可见的, 而存储过程却要将这个nested的表类型暴露到package之外, 所以Oracle会报错. 知道这个原因之后, 解决也很简单, Nested Record和Nested Table的定义放在包之外, 就行了. 需要说明的是, 需要修改一下Nested Record的定义格式(从Record类型改到Object类型).



--create one object type outside of one package
create or replace type TYPE_JOB_ROW as object
(
JOB_CODE VARCHAR(50),
JOB_NAME VARCHAR(200)
);

--create one table type outside of one package
CREATE OR REPLACE TYPE TYPE_JOB_TABLE is TABLE OF TYPE_JOB_ROW;


create or replace package PKG_TEST3 is

type type_result_table is ref cursor ;

procedure SP_GET_JOB_INFO(result_table out type_result_table) ;
end PKG_TEST3;
/
create or replace package body PKG_TEST3 is

procedure SP_GET_JOB_INFO(result_table out type_result_table)
IS
JOB_TABLE TYPE_JOB_TABLE:=TYPE_JOB_TABLE() ;
JOB_ROW TYPE_JOB_ROW ;
BEGIN
JOB_ROW:=TYPE_JOB_ROW(JOB_CODE => 'A',JOB_NAME => 'A123') ;
JOB_ROW.JOB_NAME:='A12' ; --Change the job_name from A123->A12
JOB_TABLE.extend();
JOB_TABLE(JOB_TABLE.count) := JOB_ROW ;

OPEN result_table FOR
SELECT * FROM table(JOB_TABLE) ;
END ;

end PKG_TEST3;
/

Oracle temp table

在SQL Server的存储过程中, 可以非常方便地使用临时表#TempTable, 你可以随意地将数据插入到临时表, 也可以将临时表的记录作为存储过程的结果集. #TempTable用过后, 它就干干净净地消失了, 包括表的structure.

而Oracle的临时表是global temporary table, 这表明它的定义是persistent, 只是内容是临时的. 除此之外, 临时表还有几个特点:

1. 临时表是不能加logging等选项, 所以它的redo log和I/O和普通表相比都非常小,

2. 不能为临时表指定tablespace, 临时表一定是在TEMPORARY tablespace中.

定义一般Heap表的格式通常是: CREATE TABLE Table_1(col_1 number) TABLESPACE USERS;

而定义临时表的格式通常是:

CREATE GLOBAL TEMPORARY TABLE Temp_Table_1 (a NUMBER) ON COMMIT

PRESERVE ROWS; --临时表的内容在整个Session中一直保持, 即使是一个transaction结束.

或者是:
CREATE GLOBAL TEMPORARY TABLE Temp_Table_1 (a NUMBER) ON COMMIT

DELETE ROWS; --临时表的内容仅仅在一个transaction中保持.

Monday, July 28, 2008

one Oracle Unit Test Package--PLUTO

pluto-test-framework (http://code.google.com/p/pluto-test-framework/)

The framework is written in PL/SQL and is based on the Oracle object system. The inner-workings of the system are based largely on the JUnit design, with a few PL/SQL'isms added in. The system is built to be easily extended and has hooks to interface with common test runners like Perl's Test::Harness.

PLUTO is lightweight, easy to install, and is free and open for everyone to use. Please feel free to give it a spin and comment back with any feedback that you might have.

Why can not Universal Installer create Listener Service

Problem: I tried to install a oracle server on remote computer via Windows Remote desktop connection. The remote computer is Windows 2003 std server. After fail to install Oracle 10g, I did try to install 11g. But the results were the same. --Every thing were OK except Oracle listener service missing. And i could not run the tnslsnr utility to configure.
Causing: Via Windows Remote desktop connection. It causes all listener operation to fail including create Listener service and manage listener service.

Wednesday, May 14, 2008

use PLSQL in SQLPLUS

正常情况下, SqlPlus是一个行命令工具, 你只能在其中运行标准的SQL语句或者是SqlPlus特有的命令, 比如print或desc. 但有两种方法, 可以使得SqlPlus进入PL-SQL模式(可以执行诸如dbms_out.output_line()等PL-SQL函数).

方法1:在SqlPlus的提示符下输入begin并回车, 接下来几行输入你的代码, 然后输入end并回车, 最后一行输入/
方法2:在SqlPlus的提示符下输入declare 然后回车, 然后声明变量(可选项), 接下来输入Begin并回车, 接下来几行输入你的代码, 然后输入end并回车, 最后一行输入/

格式如下:
[declare]

[variable1 type;]



--some statements;



/


示例如下:

SQL> DECLARE

v_dname VARCHAR2(14);

BEGIN

v_dname := 'HR';

dbms_output.put_line(v_dname);

END;

/

Wednesday, April 30, 2008

call stored procedure that return one result set using sqlplus Toad SqlTools

http://www.devshed.com/c/a/Oracle/Working-with-REF-CURSOR-in-PL-SQL/
PLSQL Developer调用和测试一个存储过程非常简单, 找到一个存储过程后, 在快捷菜单中选择Test菜单项目, 输入存储过程的参数, 就可以了.
1. 在SQLPlus中调用这个过程的方法
用SQLPlus来调用/测试一个自定义的存储过程, 相对比较麻烦, 不像用PLSQL Developer方便. 尤其是如果该存储过程返回一个记录集, 我用SQLPLUS一直没有调用成功过. 最主要的原因是: 原来在SQLPlus中不知道怎样使用bind variable. 下面给出一个例子.
假如在PKG_1包中有 PROC_1存储过程, 它接受一个Input参数, 然后返回一个记录集.



PROCEDURE PROC_1(IN_SRVY_ID IN VARCHAR , OUT_TABLE OUT SYS_REFCURSOR )
IS
BEGIN
OPEN OUT_TABLE FOR
SELECT * FROM SRVY WHERE SRVY.SRVY_ID=IN_SRVY_ID;
END PROC_1;

在SQLPlus中调用这个过程的方法, 然后打印出结果集

SQL> VARIABLE CURSOR_1 REFCURSOR

SQL > VARIABLE IN_SRVY_D VARCHAR2 ( 5 )

SQL > EXEC :IN_SRVY_D := 'SU07'

SQL > EXEC PKG_1.PROC_1(:IN_SRVY_D, :CURSOR_1)

SQL >PRINT :CURSOR_1

http://www.indiastudychannel.com/resources/8519-SQL-SQL-Plus-Interview-Questions-with-Answers-Set.aspx

http://lbdwww.epfl.ch/f/teaching/courses/oracle8i/server.815/a66736/ch35.htm

2. 在Toad或SQLTools就不像SQLPLus那么容易了, 主要是在于打印记录集比较复杂



--假设emp表中有三个字段分别是 eid, ename, sal. -- select eid, ename, sal from emp;
create table emp
(
ename VARCHAR2 ( 5 ) not null ,
eid NUMBER ( 6 ) not null ,
sal NUMBER ( 6 )
);

create or replace package PKG_2
IS
TYPE c_return_table IS REF CURSOR ;
PROCEDURE SP_GET_EMP_INFO(IN_EMP_ID IN VARCHAR , OUT_TABLE OUT c_return_table);
END PKG_2;


create or replace package BODY PKG_2
IS
PROCEDURE SP_GET_EMP_INFO(IN_EMP_ID IN VARCHAR , OUT_TABLE OUT c_return_table)
BEGIN
OPEN OUT_TABLE FOR
SELECT eid, ename, sal FROM EMP WHERE eid= IN_EMP_ID;
END SP_GET_EMP_INFO ;
END PKG_2;


--***************************示例说明*************************************************
--在PLSQL Developer中,调用和显示一个返回记录集的存储过程非常简单(因为它提供了Test Procedure功能),
--但对于其他工具, 却没有这样的功能, 你必须自己写代码来调用, 然后显示
--***********************************************************************************

DECLARE
IN_EMP_ID emp.eid% TYPE;
OUT_C_RETURN_TABLE PKG_2.C_RETURN_TABLE;

--如果从一个Cursor中整条整条地取数据, 则下面的类型必须和Cursor中每个字段的类型都匹配, 这包括类型必须匹配和字段个数必须一样.
TYPE REC_EMP IS RECORD(
ename VARCHAR2(5),
eid NUMBER(6),
sal NUMBER(6)
);

EMP_ROW REC_EMP;

--如果Cursor的字段和emp的字段刚好一样, 你不必先定义一个Record类型, 直接声明EMP_ROW
--EMP_ROW emp%ROWTYPE ;
BEGIN
IN_EMP_ID := 10;

--STEP1: OPEN CURSOR BY EXECUTING STORED PROCEDURE
PKG_2.SP_GET_EMP_INFO(IN_EMP_ID, OUT_C_RETURN_TABLE);

--STEP1: OR OPEN CURSOR BY SELECT STATEMENT
/*
OPEN C_EMP FOR SELECT * FROM EMP where eid=IN_EMP_ID;
*/

LOOP
--STEP2: FETCH EVERY ROW FROM CURSOR
FETCH OUT_C_RETURN_TABLE
INTO EMP_ROW;
EXIT WHEN OUT_C_RETURN_TABLE% NOTFOUND;
--PRINT EVERY ROW
DBMS_OUTPUT.PUT_LINE(EMP_ROW.eid || ' - ' || EMP_ROW.ename);
END LOOP;

--STEP3: CLOSE CURSOR
CLOSE OUT_C_RETURN_TABLE;

DBMS_OUTPUT.PUT_LINE('EXECUTION OVER.');
END;

CNOUG.net