Дизайн курсора и вопрос рефакторинга - PullRequest
0 голосов
/ 22 января 2010

У меня есть много курсоров, которые возвращают строки с одинаковыми полями: числовое поле идентификатора и поле 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)

EDIT: Я думаю, что лучший способ описать то, что я делаю, это передать ссылку на явный курсор в функцию, которая будет выполнять некоторые общие процедуры с данными, возвращаемыми курсором. Похоже, что я не могу использовать оператор open-for с курсором explcit, есть ли другой способ получить ссылку на явный курсор, чтобы я мог передать эту ссылку функции? Может быть, есть другой способ решить эту проблему?

EDIT: Копирование и вставка из моего предыдущего ответа на ответ R Van Rijn:

Я попытался объявить курсор в спецификации пакета и связать его с именем пакета: open v_curs для PKG.c_getSomeData (x); ... Это дает мне новую ошибку, говоря, что PKG.c_getSomeData должна быть функцией или массив, который будет использоваться таким образом.

UPDATE: Я говорил с нашим администратором базы данных здесь, он говорит, что невозможно установить указатель ref на явный курсор. Похоже, я не могу сделать это в конце концов. Облом. (

Ответы [ 5 ]

1 голос
/ 09 февраля 2011

ОК, поэтому краткий ответ от Oracle: «не может быть сделано!»

Краткий ответ от меня: «Да - как 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 никогда не закрывается, потому что мы не закрываем его, и клиент будет закрывать только ссылку на вложенный курсор, который был выбран для них, а не основной курсор.И мы не можем закрыть его автоматически, потому что закрытие родителя заставит закрыть вложенный курсор, который вы вернули по ссылке, - и вполне вероятно, что клиент не использовал его.

Поэтому мы должны уйтикурсор, открытый для возврата вложенного курсора.

И если пользователь попытается снова вызвать get_Cursor с новым значением ed_id, он обнаружит, что сохранение сеанса в пакете означает, что дескриптор курсора все еще находится вuse и возникнет ошибка.

Теперь мы могли бы это исправить, сначала проверив и закрыв явный курсор:

  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 перед возвратом сеанса в пул - мы можем получить действительно интересные (и невозможные для отладки) результаты!

А сколько выдоверяете разработчикам клиентского кода быть слишком усердными в этом?Да, я тоже.

Итак, краткий ответ: да, с некоторой подлостью это можно сделать, несмотря на то, что Oracle говорит, что не может.

Лучший ответ: но, пожалуйста, дона«т!Дополнительное круговое обращение и возможность утечек памяти и ошибок клиентского кода, вызывающих проблемы с данными, делают это очень пугающим предложением.

1 голос
/ 23 января 2010

Представляет ли этот тестовый скрипт и вывод то, что вы пытаетесь сделать? Вместо 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.
1 голос
/ 23 января 2010

Признаюсь, что найти ваши требования довольно сложно. Вы опубликовали много кода, но, как я предложил в своем комментарии, не те части, которые освещали бы проблему. Так что, возможно, следующее далеко от луча. Но это интересный вопрос.

Следующий код показывает, как мы можем определить общий, общий 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>
1 голос
/ 22 января 2010

относительно ошибки PLS-00222:

Идентификатор, на который ссылается функция c_getSomeData, не был объявлен или фактически представляет другой объект (например, он мог быть объявлен как процедура).

Проверьте правильность написания и объявления идентификатора. Также подтвердите, что объявление правильно размещено в структуре блока

Это означает, что вы должны создать функцию, которая на самом деле возвращает некоторые значения.

0 голосов
/ 25 января 2010

Похоже, что то, что я хотел сделать (иметь ссылку на оператор open-for на существующий явный курсор), просто не разрешено в Oracle. (

...