PLSQL入门与精通(第76章:表行数的异常处理NO_DATA_NEEDED)
上次介绍了“表·函数”,在使用表·函数时,有些注意事项要注意。
那就是使用“WHERE ROWNUM<=n”想取前几条数据相关的注意事项。
例如,请看以下我们用“WERE ROWNUM<=n”对表·函数进行查询。
SELECT * FROM TABLE (pac1.f1(5)) WHERE ROWNUM <= 2;
COL1 COL2
1 ABC1
2 ABC2
上面的表函数pac1.f1(5)是处理5次循环,每次循环返回一条数据,总共返回五条数据。
也就是说这个函数如果没有查询条件的话,可以返回5行数据。
但是,因为WHERE句有ROWNUM<=2这个条件,所以只需要前两行数据。
在这种情况下,表函数会在最初的两行中中断处理,有时会产生问题。
按顺序确认一下吧:
便于理解,在表·函数中追加了部分输出信息的处理。
CREATE OR REPLACE PACKAGE BODY pac1
IS
//
/表函数f1的定义/
//
FUNCTION f1(p1 IN NUMBER) RETURN rec_tab_type PIPELINED
IS
rec rec_type;
BEGIN
FOR I IN 1…p1 LOOP
rec.col1 := I;
rec.col2 := 'ABC’ || TO_CHAR(I);
PIPE ROW(rec);
END LOOP;
DBMS_OUTPUT.PUT_LINE('MSG:返回了所有的行’);–这是本次添加的处理
RETURN ;
END f1;
END PAC1;
/
包主体创建完成。
通过添加PUT_LINE的处理,如果处理不中断的话,在返回到最后一行时,
会输出“MSG:返回了所有的行”的信息,因此可以根据这个信息可以判断处理是否执行到了最后。
试一下效果:
SET SERVEROUTPUT ON
为了返回所有的行,不加查询条件去查询表·函数。p1参数值设为5,循环5次返回5行。
SELECT * FROM TABLE (pac1.f1(5));
COL1 COL2
1 ABC1
2 ABC2
3 ABC3
4 ABC4
5 ABC5
MSG:返回了所有的行
最后有“MSG:返回了所有的行”这样的信息。
从而判断处理到了最后。这个和想的的一样。
接着我们试着加上“WHRE COL1=3”的查询条件在看看:
SELECT * FROM TABLE (pac1.f1(5)) WHERE COL1 = 3;
COL1 COL2
3 ABC3
MSG:返回了所有的行
因为加了查询条件,所以只显示了一行。
但是,在这种情况下也出现了“MSG:返回了所有的行”的信息。
和我们预想的不太一样,但是结果就是这样子。
也就是说,虽然实际上表·函数已经返回了5行,但是根据查询的“WHERE COL1=3”的条件,
只返回了5行中满足条件的1行。
再接着,我们加上查询条件是“WHRE ROWNUM<=2”在看一下效果:
SELECT * FROM TABLE (pac1.f1(5)) WHERE ROWNUM <= 2;
COL1 COL2
1 ABC1
2 ABC2
在这种情况下,最后没有显示“MSG:返回了所有的行”的信息。
也就是说,这个表函数在中途处理就结束了。
解释如下:
表函数在PIPE ROW处理(第13行源代码)中及时一行一行返回数据,
但是如果超过ROW NUM指定的行数,继续进行PIPE ROW处理的话,
则会发生NO DATA NEEDID的例外(NO DATA FOUND例外))。
程序中又没有处理这个例外,也就是说如果在例外处理部中未记述OTHERS处理程序或NO DATA NEEDID例外处理程序,
程序会以未处理例外的形式结束。
在这种情况下,表函数里边如果用UTL_FILE包等打开文件的话,就会以例外处理直接结束程序,
文件也不会关闭,这样的程序是有问题的。
如果是用到游标的话,游标也不会被关闭,反复执行这样的处理,数据库开启游标数就会达到上限
并发生错误。
因此,表·函数里边要进行NO DATA NEEDID例外处理,相关资源(譬如打开的文件,或者游标等)要进行关闭。
刚才的例子中我们追加相应的NO DATA NEEDID例外处理程序。结果如下:
注意:因为这个例子处理中没有打开文件和游标,所以这个例外处理没有太大的意义,
但是,我们只能通过特定的方式,确认一下这个例外是否会发生。
CREATE OR REPLACE PACKAGE BODY pac1
IS
//
/表函数f1的定义/
//
FUNCTION f1(p1 IN NUMBER) RETURN rec_tab_type PIPELINED
IS
rec rec_type;
BEGIN
FOR I IN 1…p1 LOOP
rec.col1 := I;
rec.col2 := 'ABC’ || TO_CHAR(I);
PIPE ROW(rec);
END LOOP;
DBMS_OUTPUT.PUT_LINE('MSG:返回了所有行’);
RETURN ;
EXCEPTION-追加
WHEN NO DATA NEEDED THEN–追加
DBMS_OUTPUT.PUT_LINE('MSG:处理中途停止了’);–本次添加的处理
RETURN;–添加
END f1;
END PAC1;
/
包主体创建完成。
实际去看看吧。
SELECT * FROM TABLE (pac1.f1(5)) WHERE ROWNUM <= 2;
COL1 COL2
1 ABC1
2 ABC2
MSG:处理中途停止
最后的信息「MSG:处理中途停止」出现了。
上述函数没有打开游标和文件等,所以异常处理没什么意义,
表·函数中如果有打开文件或者游标的话,上述的“MSG:返回了所有行”“MSG:处理中途停止了”
等输出信息的处理部分就需要该成关闭文件或者游标的处理了。
因此,管道表函数在“WEHERE ROWNUM<=n”这样的查询条件下内部会发生“NO DATA NEEDID”例外,
因此在打开文件和游标的情况下,除了通常的关闭处理外,还需要使用NO DATA NEEDID例外处理程序
来实施关联的关闭处理。
那么,作为简单的例子,最后把这次的pac1.f1函数改写成使用明示游标的内容。
CREATE OR REPLACE PACKAGE BODY pac1
IS
/*********************************************************************/
/表·函数f1的定义(按照部门编号对应的返回员工)/
/*********************************************************************/
FUNCTION f1(p1 IN NUMBER) RETURN rec_tab_type PIPELINED
IS
–申明游标:返回指定部门编号的员工
CURSOR c1 is SELECT * FROM EMP WHERE DEPTNO = p1;
emp_rec%rowtype;–游标的行记录变量
rec rec_type;–表函数的行记录变量
BEGIN
OPEN C1;–游标打开
LOOP–重复以下处理
FETCH c1 into emp_rec;–取得1行数据
EXIT WHEN c1%NOTFTOUND;–没有取得的数据时,退出循环
rec.col1 := emp_rec.empno;–设置表·函数行的col 1列的值
rec.col2 := emp_rec.ename;–设置表·函数行的col 2列的值
PIPE ROW(rec);–通过管道返回表·函数行
END LOOP;
CLOSE c1;–循环结束后关闭游标
RETURN; --结束表函数
EXCEPTION
WHEN NO DATA NEEDEDED THEN
CLOSEc1;–关闭游标
RETURN;–结束表函数
END f1;
END PAC1;
/
包主体创建完成。
那么试着实行一下吧。首先是部门号30号员工的查询。
SELECT * FROM TABLE (pac1.f1(30));
COL1 COL2
7499 ALLEN
7521 WARD
7654 MARIN
7698 BLAKE
7844 TURNER
7900 JAMES
选择了6行。
那么接下来,我们加上ROWNUM条件,只查询前两行。
SQL> SELECT * FROM TABLE (pac1.f1(30)) WHERE ROWNUM <= 2;
COL1 COL2
7499 ALLEN
7521 WARD
结果会正确地返回。
在有「WHRE ROWNUM<=n」条件和没有条件的情况下,这个表函数结束的时候,
游标是一直打开的没有关闭。
本次到此为止。