光标设计和重构问题

时间:2010-01-22 16:59:58

标签: oracle refactoring plsql cursor

我有许多游标都返回具有相同字段的行:数字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)

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

修改 复制并粘贴我之前对R Van Rijn回复的回复:

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

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

5 个答案:

答案 0 :(得分:1)

关于错误PLS-00222:

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

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

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

答案 1 :(得分: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.

答案 2 :(得分: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>

答案 3 :(得分:1)

好的,所以Oracle的简短回答是:“无法完成!”

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

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

e.g。)

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游标永远不会关闭,因为我们不关闭它,客户端只会关闭为它们选择的嵌套游标的引用 - 而不是主游标。并且我们无法自动关闭它,因为父对象的封闭会强制关闭您通过引用返回的嵌套游标 - 而且完全可能是客户端没有使用它。

所以我们必须打开游标才能返回嵌套游标。

如果用户尝试使用新值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,那么我们最终会得到一些非常有趣(并且无法调试)的结果!

您是否相信客户端代码的维护者对此非常勤奋?是的 - 我也是。

所以简短的回答是:是的,尽管甲骨文表示不能这样做,但可能会有一些偷偷摸摸的事情。

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

答案 4 :(得分:0)

在Oracle中,似乎不允许我想要做的事情(有open-for语句引用现有的显式游标)。 :(