0

我有许多游标都返回具有相同字段的行:一个数字 ID 字段和一个 XMLType 字段。每次我访问这些游标之一(每个游标现在都有自己的访问功能)时,我都会经历相同的模式:

--query behind cursor is designed to no more than one row.
for rec in c_someCursor(in_searchKey => local_search_key_value) loop
    v_id := rec.ID
    v_someXMLVar := rec.XMLDataField
end loop;

if v_someXMLVar is null then
    /* A bunch of mostly-standard error handling and logging goes here */
end if;

exception
    /* all cursor access functions have the same error-handling */
end;

随着模式变得更加明显,将其集中在一个函数中是有意义的:

    function fn_standardCursorAccess(in_cursor in t_xmlCursorType, in_alt in XMLType) return XMLType is
            v_XMLData XMLType;
        begin
            dbms_application_info.set_module(module_name => $$PLSQL_UNIT, action_name => 'fn_standardCursorAccess');
            loop
                fetch in_cursor
                    into v_XMLData;
                exit when in_cursor%notfound;
            end loop;
            /*some additional standard processing goes here*/
            return v_XML;
        exception
        /*standard exception handling happens here*/
    end;

我遇到的问题是调用这个函数。我现在必须这样称呼它:

open v_curs for select /*blah blah blah*/ where key_field = x and /*...*/;
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;

我想做的是这样称呼它:

open v_curs for c_getSomeData(x);
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;

...原因是要尽量减少对我的代码的更改量(我不想将所有这些游标剪切/粘贴到依赖它们的函数,并且在多个函数依赖于同一个游标的情况下,我必须将它包装在一个新函数中)。

不幸的是,这不起作用,Oracle 返回一个错误说

Error: PLS-00222: no function with name 'C_GETSOMEDATA' exists in this scope

我想要做的甚至可能吗?

(Oracle 版本为 10.2)

编辑: 我认为描述我正在做的更好的方法是将显式游标的引用传递给一个函数,该函数将对游标返回的数据执行一些常见的例程。看来我不能使用带有显式游标的 open-for 语句,有没有其他方法可以获取对显式游标的引用,以便我可以将该引用传递给函数?也许还有其他方法可以解决这个问题?

编辑: 复制和粘贴我之前对 R Van Rijn 的回复的回复:

我尝试在包规范中声明光标,并用包名引用它:open v_curs for PKG.c_getSomeData(x);... 这给了我一个新错误,说 PKG.c_getSomeData 必须是一个函数或数组那样使用。

更新: 我在这里与我们的 DBA 交谈过,他说不可能让 ref 游标指向显式游标。看来我毕竟不能这样做。真可惜。:(

4

5 回答 5

1

好的,所以 Oracle 的简短回答是:“做不到!”

我的简短回答是:“是的 - 就像甲骨文会阻止我一样!所以是的,你可以......但你需要偷偷摸摸......哦,是的,并且有一两个'但是'......事实上……呃!”

那么,如何通过引用传递显式游标呢?通过使用 CURSOR() 构造将其嵌套到另一个游标中!

例如)

CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
   function get_cursor(ed_id number) return sys_refcursor;
end;
/

CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
   function get_cursor(ed_id number) return sys_refcursor
   is
      test_Cur sys_refcursor;

      cursor gettest is
        select CURSOR( -pass our actual query back as a nested CURSOR type
           select ELCTRL_EVNT_ELCTRL_DISTRCT_ID, 
                  ELECTORAL_DISTRICT_ID, 
                  ELECTORAL_EVENT_ID 
           from  ELCTRL_EVNT_ELCTRL_DISTRCT
           where electoral_District_id = ed_id)
        from dual;
   begin
      open gettest;
      fetch gettest into test_Cur;
      return test_Cur;       
   end;
end;
/

那么这个解决方案有什么问题呢?它有泄漏!外部 gettest 游标永远不会关闭,因为我们不会关闭它,并且客户端只会关闭对为它们选择的嵌套游标的引用 - 而不是主游标。而且我们不能自动关闭它,因为 closign 父级会强制关闭您通过引用返回的嵌套游标 - 并且客户端完全有可能没有使用它。

所以我们必须让游标保持打开状态才能返回嵌套游标。

如果用户尝试使用新的 ed_id 值再次调用 get_Cursor,他们会发现包中的会话持久性意味着游标句柄仍在使用中,并且会引发错误。

现在,我们可以通过首先检查并关闭显式游标来解决这个问题:

  if gettest%isopen then
    close gettest;
  end if;
  open gettest;
  fetch gettest into test_Cur;
  return test_Cur;       

但是仍然 - 如果用户不再调用它怎么办?Oracle 垃圾收集游标需要多长时间?有多少用户在运行多少个会话,调用多少个使用这个结构的函数,在完成游标后会保持打开状态?最好依靠 huuuuuge 开销来让所有打开的游标留在周围!

不,您需要让用户进行回调以明确关闭它,否则您将阻塞数据库。但是这样做需要更改显式游标的范围,以便两个函数都可以访问它:所以我们需要在包范围内进行,而不是在函数范围内

CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
   function get_cursor(ed_id number) return sys_refcursor;
   function close_cursor return sys_refcursor;
end;
/

CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as

   cursor l_gettest(p_ed_id in number) is
        select CURSOR(
         select ELCTRL_EVNT_ELCTRL_DISTRCT_ID, ELECTORAL_DISTRICT_ID, ELECTORAL_EVENT_ID 
         from  ELCTRL_EVNT_ELCTRL_DISTRCT
         where electoral_District_id = p_ed_id)
        from dual;


   function get_cursor(ed_id number) return sys_refcursor
   is
      l_get_Cursor sys_refcursor;
   begin
      open l_gettest (ed_id);
      fetch l_gettest into l_get_Cursor;
      return l_get_cursor;       
   end;

   function close_cursor return sys_refcursor
   is
   begin
      if l_gettest%isopen then
         close l_gettest;
      end if;
      return pkg_common.generic_success_cursor;
   end;      

end;
/

好的,堵住了漏水处。除了它花费了我们一次网络往返而不是硬解析,......哦等等 - 并且除了将绑定变量嵌入到在此级别声明的显式游标中可能会导致其自身的范围问题,这就是我们的原因一开始就想做这个!

哦,在会话池环境中,两个用户可以互相踩到对方的光标吗?如果他们在将会话返回到池之前不是非常小心地执行 open-fetch-close - 我们可能会得到一些非常有趣(并且无法调试)的结果!

您在多大程度上相信客户端代码的维护者会在这方面格外勤奋?我也是。

所以简短的回答是:是的,尽管甲骨文说它做不到,但稍微偷偷摸摸地可以做到。

更好的答案是:但请不要!额外的往返行程以及内存泄漏和客户端代码错误导致数据问题的可能性使这成为一个非常可怕的提议。

于 2011-02-09T17:04:33.137 回答
1

此测试脚本和输出是否代表您正在尝试做的事情?而不是open v_curs for c_getSomeData(x);我将光标变量 = 设置为函数的输出。

我们的测试数据:

set serveroutput on

--create demo table
drop table company;
create table company 
(
 id number not null,
 name varchar2(40)
);

insert into company (id, name) values (1, 'Test 1 Company');
insert into company (id, name) values (2, 'Test 2 Company');
insert into company (id, name) values (3, 'Test 3 Company');

commit;

创建包

create or replace package test_pkg as

  type cursor_type is ref cursor;

  function c_getSomeData(v_companyID number) return cursor_type;

end test_pkg;
/

create or replace package body test_pkg as

  function c_getSomeData(v_companyID number) return cursor_type
  is 
    v_cursor cursor_type;
  begin

    open v_cursor for
    select id,
           name
      from company
     where id = v_companyID;

    return v_cursor;
  end c_getSomeData;

end test_pkg;
/

运行我们的程序

declare
  c test_pkg.cursor_type;
  v_id company.id%type;
  v_name company.name%type;
begin
  c := test_pkg.c_getSomeData(1);

  loop 
    fetch c
    into  v_id, v_name;
    exit when c%notfound;
    dbms_output.put_line(v_id || ' | ' || v_name);
  end loop;

  close c;

end;
/

1 | Test 1 Company

PL/SQL procedure successfully completed.
于 2010-01-22T22:07:28.307 回答
1

我承认发现您的要求有点难以预测。您已经发布了很多代码,但正如我在评论中所建议的那样,不是可以说明问题的部分。因此,以下可能是偏离光束的方式。但这是一个有趣的问题。

下面的代码展示了我们如何定义一个通用的、通用的 REF CURSOR,用来自不同查询的特定数据填充它,然后以标准化的方式处理它们。再次,如果这不符合您的业务逻辑,我深表歉意;如果是这种情况,请编辑您的问题以解释我在哪里制作了灯笼裤..

这是通用参考光标。...

create or replace package type_def is
    type xml_rec is record (id number, payload xmltype);
    type xml_cur is ref cursor return xml_rec;
end type_def;
/

这是标准处理器

create or replace procedure print_xml_cur 
    ( p_cur in type_def.xml_cur )
is
    lrec type_def.xml_rec;
begin
    loop
        fetch p_cur into lrec;
        exit when p_cur%notfound;
        dbms_output.put_line('ID='||lrec.id);
        dbms_output.put_line('xml='||lrec.payload.getClobVal());
    end loop;
    close p_cur;
end print_xml_cur;
/

返回具有不同数据的标准游标的两个过程....

create or replace function get_emp_xml
    ( p_id in emp.deptno%type )
    return type_def.xml_cur
is
    return_value type_def.xml_cur;
begin
    open return_value for 
        select deptno
               , sys_xmlagg(sys_xmlgen(ename))
        from emp
        where deptno = p_id
        group by deptno;
    return return_value;
end get_emp_xml;
/

create or replace function get_dept_xml
    ( p_id in dept.deptno%type )
    return type_def.xml_cur
is
    return_value type_def.xml_cur;
begin
    open return_value for 
        select deptno
               , sys_xmlagg(sys_xmlgen(dname))
        from dept
        where deptno = p_id
        group by deptno;
    return return_value;
end get_dept_xml;
/

现在让我们把它们放在一起......

SQL> set serveroutput on size unlimited
SQL>
SQL> exec print_xml_cur(get_emp_xml(40))
ID=40
xml=<?xml
version="1.0"?>
<ROWSET>
<ENAME>GADGET</ENAME>
<ENAME>KISHORE</ENAME>
</ROWSET>


PL/SQL procedure successfully completed.

SQL> exec print_xml_cur(get_dept_xml(20))
ID=20
xml=<?xml version="1.0"?>
<ROWSET>
<DNAME>RESEARCH</DNAME>
</ROWSET>


PL/SQL procedure successfully completed.

SQL>
于 2010-01-23T00:41:19.483 回答
1

关于错误 PLS-00222:

被引用为函数“c_getSomeData”的标识符未声明或实际上表示另一个对象(例如,它可能已声明为过程)。

检查标识符的拼写和声明。还要确认声明正确放置在块结构中

这意味着您必须创建一个实际返回一些值的函数。

于 2010-01-22T17:52:15.263 回答
0

看来,open-forOracle 根本不允许我想做的事情(让语句引用现有的显式游标)。:(

于 2010-01-25T16:07:34.327 回答