簡體   English   中英

游標設計和重構問題

[英]Cursor design and refactoring question

我有許多游標都返回具有相同字段的行:數字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的答復:

我嘗試在程序包規范中聲明游標,並使用程序包名稱對其進行引用:為PKG.c_getSomeData(x);打開v_curs; ...這給了我一個新的錯誤,說PKG.c_getSomeData必須是函數或數組用那種方式。

更新:我在這里與我們的DBA進行了交談,他說不可能將ref游標指向一個顯式游標。 看來我畢竟做不到。 游民。 :(

關於錯誤PLS-00222:

沒有聲明被引用為函數'c_getSomeData'的標識符,或者實際上代表了另一個對象(例如,它可能已聲明為過程)。

檢查標識符的拼寫和聲明。 還要確認聲明已正確放置在塊結構中

這意味着您必須創建一個實際上返回一些值的函數。

此測試腳本和輸出是否表示您要執行的操作? 而不是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.

我承認發現您的要求微不足道。 您已經發布了很多代碼,但是正如我在評論中所建議的那樣,不是那些可以闡明問題的部分。 因此,以下可能是偏離光束的方法。 但這是一個有趣的問題。

以下代碼顯示了我們如何定義通用的通用REF CURSOR,並使用來自不同查詢的特定數據填充它,然后以標准化方式對其進行處理。 再次致歉,如果這不符合您的業務邏輯; 如果是這種情況,請修改您的問題,以說明我在哪里制造了燈籠褲。

這是通用的ref游標。 ...

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>

好的,因此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垃圾回收游標需要多長時間? 多少用戶正在運行多少會話,調用多少個使用此構造的函數,將在使用完游標后使游標保持打開狀態? 最好依靠巨大的開銷,以使所有打開的游標都躺在上面!

不,您需要讓用戶執行回調以顯式關閉它,否則您將阻塞數據庫。 但這需要更改顯式游標的范圍,以便兩個函數都可以訪問它:因此,我們需要在包范圍而不是函數范圍內進行操作

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;
/

好,堵​​住了泄漏。 除了它使我們花費了網絡往返路程,而不是進行硬解析之外,...等等,並且除了將綁定變量嵌入到在此級別聲明的顯式游標之外,很可能會導致自身的作用域問題,這就是我們這樣做的原因想要首先做到這一點!

哦,在會話池環境中,兩個用戶可以互相踩一下光標嗎? 如果他們在將會話返回到池之前不太謹慎地進行打開-獲取-關閉-我們可能會得到一些非常有趣(且無法調試)的結果!

您對客戶機代碼的維護者在此方面的額外努力有多大的信任? 我也是。

因此,簡短的答案是:是的,盡管Oracle表示無法做到,但只要稍稍作弊就可以完成。

更好的答案是:但是請不要! 額外的往返行程以及潛在的內存泄漏和客戶端代碼錯誤導致數據問題,這使這成為一個非常可怕的主張。

看來,我想做的事情(有一個引用現有的顯式游標的open-for語句)在Oracle中是完全不允許的。 :(

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM