Oracle изменить sys refcursor и вернуть измененный курсор в plsql - PullRequest
1 голос
/ 22 января 2020

Я пытаюсь создать процедуру, которая принимает sys refcursor как параметр out и изменяет его на основе логики c, объясненной в комментариях к приведенному ниже коду.

TYPE t_params IS
  TABLE OF VARCHAR2(32767 CHAR);
  /
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary   IN t_params,
p_values         IN t_params
)
/*

p_cursor IN OUT SYS_REFCURSOR
-- contains a single row {empId:123, ename:"king", mgr:"Porter",deptNo:200}
p_array_binary  IN t_params
-- contains one binary value corresponding to each column in above cursor ["1","0","1","1"] 
p_values  IN t_params
-- contains one binary value corresponding to each column in above cursor ["123","king2","new manager","200"]



*/
IS

BEGIN
    /*
        Based on p_array_binary 
           if binary value 0 then take cursor should retain value as it is fro corresponding column
           if binary value 1 then cusrsor should have the correspondoing column value from p_values 


    In short, the out cursor should be      {empId:123, ename:"king", mgr:"new manager", deptNo:200}     

*/

END;
/

Любая помощь в этом отношении будет высоко ценится.

1 Ответ

2 голосов
/ 23 января 2020

Если бы вы знали структуру курсора ref - это всегда было четыре столбца с указанными типами данных - тогда это было бы относительно просто:

CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary   IN t_params,
p_values         IN t_params
)
IS
  l_empid number;
  l_ename varchar2(30);
  l_mgr varchar2(30);
  l_deptNo number;
BEGIN
  -- get original values into local variables
  fetch p_cursor into l_empId, l_ename, l_mgr, l_deptNo;
  -- re-open cursor using either local variables of p_values depending on p_binary flag
  open p_cursor for
    select
      case when p_array_binary(1) = '1' then to_number(p_values(1)) else l_empId end as empId,
      case when p_array_binary(2) = '1' then p_values(2) else l_ename end as ename,
      case when p_array_binary(3) = '1' then p_values(3) else l_mgr end as mgr,
      case when p_array_binary(4) = '1' then to_number(p_values(4)) else l_deptNo end as deptNo
    from dual;
END;
/

Демонстрация с использованием данных примера через SQL* Plus / SQL Переменные связывания Developer / SQLcl:

var rc refcursor;

begin
  open :rc for
    select 123 as empId, 'king' as ename, 'Porter' as mgr, 200 as deptNo
    from dual;

  modify_cursor(:rc, t_params('1', '0', '1', '1'), t_params('123', 'king2', 'new manager', '200'));
end;
/

print rc

     EMPID ENAME                            MGR                                  DEPTNO
---------- -------------------------------- -------------------------------- ----------
       123 king                             new manager                             200

db <> fiddle

Поскольку вы не знаете структуру заранее, вам придется используйте Dynami c SQL, что немного сложнее. Вот схема:

CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary   IN t_params,
p_values         IN t_params
)
IS
  l_c integer;
  l_col_cnt integer;
  l_desc_t dbms_sql.desc_tab3;
  l_varchar2 varchar2(32767 char);
  l_values t_params := new t_params();
  l_result integer;
BEGIN
  -- convert ref cursor to dbms_sql cursor
  l_c := dbms_sql.to_cursor_number(rc => p_cursor);
  -- analyse the cursor (columns, data types)
  dbms_sql.describe_columns3(c => l_c, col_cnt => l_col_cnt, desc_t => l_desc_t);
  -- optionally check l_col_cnt matches sise of t_params arguments?
  l_values.extend(l_col_cnt);

  -- define each column for fetch; here you're treating everything as strings,
  -- which will cause issues with some other data types
  for i in 1..l_col_cnt loop
    dbms_sql.define_column(c => l_c, position => i, column => l_varchar2, column_size => 32767);
  end loop;

  -- fetch original values - only one row to worry about so no loop
  l_result := dbms_sql.fetch_rows(c => l_c);

  for i in 1..l_col_cnt loop
    -- depending on p_array_binary, set l_values from either fetched data or p_values
    if p_array_binary(i) = '1' then
      l_values(i) := p_values(i);
    else
      -- this forces everything to varchar2, which is OK (ish) for your sample data;
      -- if you have other data types e.g. dates then you will probably want type-specific
      -- handling so you can control the conversions - which affects this, define_column
      -- and the final cursor to retrieve the values. But you have the same issue with p_values.
      dbms_sql.column_value(c => l_c, position => i, value => l_values(i));
    end if;
  end loop;

  -- finished with original cursor, so close it
  dbms_sql.close_cursor(c => l_c);

  -- re-open ref cursor using l_values data, with another dynamic SQL statement
  l_varchar2 := 'select ';
  for i in 1..l_col_cnt loop
    if i > 1 then
      l_varchar2 := l_varchar2 || ', ';
    end if;
    if l_desc_t(i).col_type = 2 then
      l_varchar2 := l_varchar2 || l_values(i);
    else
      l_varchar2 := l_varchar2 || '''' || l_values(i) || '''';
    end if;
    l_varchar2 := l_varchar2 || ' as "' || l_desc_t(i).col_name || '"';
  end loop;
  l_varchar2 := l_varchar2 || ' from dual';
  open p_cursor for l_varchar2;
END;
/

Запуск точно такого же демонстрационного блока дает:

     EMPID ENAM MGR             DEPTNO
---------- ---- ----------- ----------
       123 king new manager        200

db <> fiddle

Вы можете добавить обработку для других типов данных, если необходимо, обработка ошибок и т. д. c.

Подробнее о dbms_ sql.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...