Oracle 修改 sys refcursor 并在 PL/SQL 中返回修改后的游标

Posted

技术标签:

【中文标题】Oracle 修改 sys refcursor 并在 PL/SQL 中返回修改后的游标【英文标题】:Oracle modify sys refcursor and return the modified cursor in PL/SQL 【发布时间】:2020-01-22 15:11:47 【问题描述】:

我正在尝试创建一个过程,它将 sys refcursor 作为 in 参数,并根据下面代码中 cmets 中解释的逻辑对其进行修改

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

我们将非常感谢您在这方面的任何帮助。

【问题讨论】:

您不能更改光标。您只能使用相应的数据创建一个新的。 是的。也许我可以创建一个新光标并将其重新分配给输出参数 p_cursor。但是如何实现呢? 或者有什么办法可以将游标单行结果转成数组 游标中的单行是具有四个属性的单个 JSON 值,还是四列?无论哪种方式,它总是四个 - 都在光标中,并且两个 t_params 参数中的四个元素 - 还是会有所不同? 它的四列。它不会总是四个。它可以变化。 t_params 中的元素个数取决于引用光标中的列数 【参考方案1】:

如果您知道 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

由于你事先不知道结构,你将不得不使用动态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

如果需要,您可以添加对其他数据类型的处理、错误处理等。

阅读更多关于dbms_sql的信息。

【讨论】:

现在太棒了。

以上是关于Oracle 修改 sys refcursor 并在 PL/SQL 中返回修改后的游标的主要内容,如果未能解决你的问题,请参考以下文章

Oracle JDBC从过程中获取SYS_REFCURSOR

Oracle:更新行的过程,并在 sys_refcursor 中返回更新的行

Oracle ref cursor和sys_refcursor

Oracle - pl sql 从 SYS_REFCURSOR 中选择

oracle 合并多个sys_refcursor

如何在 oracle 中使用 sys_refcursor 创建动态 sql