【问题标题】:Cursor as in parameter - refactoring a procedure光标作为参数 - 重构过程
【发布时间】:2021-11-23 00:29:03
【问题描述】:

我有很多和这个类似的功能:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE REP_HELPER1 (myIdx IN BINARY_INTEGER, from_d IN DATE, rep_table IN OUT rep_table_T) IS
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



CURSOR myCUR1 IS SELECT myField1,
                        myField2,
                        myField3,
                        myField4,
                        myField5,
                        myField6,
                        myField7,
                        myField8,
                        myField9,
                        myField10,
                        myField11,
                        myField12,
                        myField13,
                        myField14,
                        myField15,
                        myField16,
                        myField17,
                        myField18,
                        myField19,
                        myField20,
                        myField21,
                        myField22,
                        myField23,
                        myField24,
                        myField25,
                        myField26,
                        myField27,
                        myField28,
                        myField29,
                        myField30,
                        myField31
                   FROM myTable;
BEGIN 
    -- I wish to move the part below to different procedure
    OPEN myCUR1;
    FETCH myCUR1 INTO rep_table(myIdx).day1,  rep_table(myIdx).day2,  rep_table(myIdx).day3,  rep_table(myIdx).day4,  rep_table(myIdx).day5,
                      rep_table(myIdx).day6,  rep_table(myIdx).day7,  rep_table(myIdx).day8,  rep_table(myIdx).day9,  rep_table(myIdx).day10,
                      rep_table(myIdx).day11, rep_table(myIdx).day12, rep_table(myIdx).day13, rep_table(myIdx).day14, rep_table(myIdx).day15,
                      rep_table(myIdx).day16, rep_table(myIdx).day17, rep_table(myIdx).day18, rep_table(myIdx).day19, rep_table(myIdx).day20,
                      rep_table(myIdx).day21, rep_table(myIdx).day22, rep_table(myIdx).day23, rep_table(myIdx).day24, rep_table(myIdx).day25,
                      rep_table(myIdx).day26, rep_table(myIdx).day27, rep_table(myIdx).day28, rep_table(myIdx).day29, rep_table(myIdx).day30,
                      rep_table(myIdx).day31;
    CLOSE myCUR1;
END  REP_HELPER1; 

我希望在单独的通用程序中完成从open myCUR; close myCUR; 的部分。因为我有很多像上面这样的功能,光标总是不同的。所以我想要一个程序来完成打开、获取、关闭部分:

PROCEDURE PB_HELPER_READ_INTO_DAYS(nIndex IN BINARY_INTEGER, myCUR by reference, rep_table IN OUT rep_table_T)

在plsql中可以吗?

编辑: 根据你的线索,我是这样写的:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE REP_HELPER1 (myIdx IN BINARY_INTEGER, from_d IN DATE, rep_table IN OUT rep_table_T) IS
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
       
myCUR1 SYS_REFCURSOR; 
        
BEGIN 
            
         OPEN myCUR1 FOR SELECT myField1,
                                myField2,
                                myField3,
                                myField4,
                                myField5,
                                myField6,
                                myField7,
                                myField8,
                                myField9,
                                myField10,
                                myField11,
                                myField12,
                                myField13,
                                myField14,
                                myField15,
                                myField16,
                                myField17,
                                myField18,
                                myField19,
                                myField20,
                                myField21,
                                myField22,
                                myField23,
                                myField24,
                                myField25,
                                myField26,
                                myField27,
                                myField28,
                                myField29,
                                myField30,
                                myField31
                           FROM myTable;
                 MY_READ(myIdx , myCUR1, rep_table) 
    END  REP_HELPER1; 
    
PROCEDURE MY_READ(myIdx IN BINARY_INTEGER, cur IN SYS_REFCURSOR, rep_table IN OUT rep_table_T) IS
        
    BEGIN
     
            FETCH cur INTO rep_table(myIdx).day1,  rep_table(myIdx).day2,  rep_table(myIdx).day3,  rep_table(myIdx).day4,  rep_table(myIdx).day5,
                                  rep_table(myIdx).day6,  rep_table(myIdx).day7,  rep_table(myIdx).day8,  rep_table(myIdx).day9,  rep_table(myIdx).day10,
                                  rep_table(myIdx).day11, rep_table(myIdx).day12, rep_table(myIdx).day13, rep_table(myIdx).day14, rep_table(myIdx).day15,
                                  rep_table(myIdx).day16, rep_table(myIdx).day17, rep_table(myIdx).day18, rep_table(myIdx).day19, rep_table(myIdx).day20,
                                  rep_table(myIdx).day21, rep_table(myIdx).day22, rep_table(myIdx).day23, rep_table(myIdx).day24, rep_table(myIdx).day25,
                                  rep_table(myIdx).day26, rep_table(myIdx).day27, rep_table(myIdx).day28, rep_table(myIdx).day29, rep_table(myIdx).day30,
                                  rep_table(myIdx).day31;
                CLOSE cur;
 END MY_READ;

【问题讨论】:

  • 所有函数都使用rep_table,是否所有游标都有与其完全匹配的列?
  • 所有游标都有 varchar 类型的列,并且所有游标总是有 31 列。
  • 好的,听起来你可以有一个函数或过程接受一个引用光标并填充一个rep_table

标签: oracle plsql database-cursor


【解决方案1】:

可行的方法是创建一个包,全局声明游标并在您想要的任何过程中使用它。例如:

SQL> create or replace package pkg_test is
  2    procedure p1;
  3  end;
  4  /

Package created.

SQL> create or replace package body pkg_test is
  2    cursor c1 is select * from dept;
  3    c1r c1%rowtype;
  4
  5
  6    procedure p1 is
  7    begin
  8      open  c1;
  9      fetch c1 into c1r;
 10      close c1;
 11    end p1;
 12  end;
 13  /

Package body created.

SQL>

这行不通:在一个过程中声明游标并在另一个过程中使用它:

SQL> create or replace package pkg_test is
  2    procedure p1;
  3    procedure p2;
  4  end;
  5  /

Package created.

SQL> create or replace package body pkg_test is
  2    procedure p1 is
  3      cursor c1 is select * from dept;
  4      c1r c1%rowtype;
  5    begin
  6      null;
  7    end p1;
  8
  9
 10    procedure p2 is
 11    begin
 12      open  c1;
 13      fetch c1 into p1.c1r;
 14      close c1;
 15    end p2;
 16  end;
 17  /

Warning: Package Body created with compilation errors.

SQL> show err
Errors for PACKAGE BODY PKG_TEST:

LINE/COL ERROR
-------- -----------------------------------------------------------------
12/5     PL/SQL: SQL Statement ignored
12/11    PLS-00201: identifier 'C1' must be declared
13/5     PL/SQL: SQL Statement ignored
13/11    PLS-00201: identifier 'C1' must be declared
14/5     PL/SQL: SQL Statement ignored
14/11    PLS-00201: identifier 'C1' must be declared
SQL>

此外,您不能使用“所有者”过程的前缀来引用它:

SQL> create or replace package body pkg_test is
  2    procedure p1 is
  3      cursor c1 is select * from dept;
  4      c1r c1%rowtype;
  5    begin
  6      null;
  7    end p1;
  8
  9
 10    procedure p2 is
 11    begin
 12      open  p1.c1;
 13      fetch p1.c1 into p1.c1r;
 14      close p1.c1;
 15    end p2;
 16  end;
 17  /

Warning: Package Body created with compilation errors.

SQL> show err
Errors for PACKAGE BODY PKG_TEST:

LINE/COL ERROR
-------- -----------------------------------------------------------------
12/5     PL/SQL: SQL Statement ignored
12/14    PLS-00225: subprogram or cursor 'P1' reference is out of scope
13/5     PL/SQL: SQL Statement ignored
13/11    PLS-00225: subprogram or cursor 'P1' reference is out of scope
14/5     PL/SQL: SQL Statement ignored
14/11    PLS-00225: subprogram or cursor 'P1' reference is out of scope
SQL>

【讨论】:

    【解决方案2】:

    您可以在任何过程或函数之外的包规范中定义光标。然后在游标有效的任何地方使用该游标(作为参考游标除外)。包括包内的任何过程/函数或任何独立的过程/函数,甚至是匿名块。请务必在包外的任何地方引用package_name.cursor_name。见demo)

    create or replace package pkg_test is
      cursor c_dept is select * from dept;
      
      procedure p1;
      procedure p2;  
    end pkg_test;
    / 
    

    这使得游标的维护非常容易,因为只有一个定义,所以只有一个维护位置。

    【讨论】:

    • 我有很多不同的游标。相同的是模式(相同的文件类型和相同数量的字段)。
    【解决方案3】:

    您只能将FETCHCLOSE 放在不同的过程中。会是这样(当你只有一个 OUT 参数时,我更喜欢FUNCTION):

    CREATE OR REPLACE FUNCTION REP_HELPER (myIdx IN BINARY_INTEGER, from_d IN DATE) RETURN SYS_REFCURSOR IS    
       myCur SYS_REFCURSOR;    
    BEGIN
    
       OPEN myCur FOR
       SELECT myField1, ...
       FROM myTable;
       RETURN myCur;
    
    END REP_HELPER; 
    

    并像这样使用它:

    DECLARE
        cur SYS_REFCURSOR;
    BEGIN 
        
        cur := REP_HELPER(...);    
        FETCH cur INTO ...
    
        CLOSE cur;
    END;
    

    更高级的解决方案是使用 DBMS_SQL 包的动态 SQL:

    CREATE OR REPLACE FUNCTION REP_HELPER(myIdx IN BINARY_INTEGER, from_d IN DATE) RETURN NUMBER IS
    
      curid NUMBER := DBMS_SQL.OPEN_CURSOR;
      sql_stmt VARCHAR2(32000);
    BEGIN
    
      sql_stmt := 'SELECT myField1, ... FROM myTable';
      DBMS_SQL.PARSE(curid, sql_stmt, DBMS_SQL.NATIVE);
      RETURN  curid;
    
    END REP_HELPER;
    
    DECLARE
        cur SYS_REFCURSOR;
        curid NUMBER;
        ret INTEGER;
    BEGIN 
        
        curid := REP_HELPER(...);    
        ret := DBMS_SQL.EXECUTE(curid);
       -- Switch from DBMS_SQL to native dynamic SQL
        cur := DBMS_SQL.TO_REFCURSOR(curid);
        FETCH cur INTO ...
    
        CLOSE cur;
    END;
    

    CREATE OR REPLACE PROCEDURE REP_HELPER(curid IN OUT NUMBER, myIdx IN BINARY_INTEGER, from_d IN DATE) IS
    
      sql_stmt VARCHAR2(32000);
    BEGIN
    
      sql_stmt := 'SELECT myField1, ... FROM myTable';
      DBMS_SQL.PARSE(curid, sql_stmt, DBMS_SQL.NATIVE);
    
    END REP_HELPER;
    
    
    DECLARE
        cur SYS_REFCURSOR;
        curid NUMBER;
        ret INTEGER;
    BEGIN 
        
        curid NUMBER := DBMS_SQL.OPEN_CURSOR;
        REP_HELPER(curid, ...);    
        ret := DBMS_SQL.EXECUTE(curid);
       -- Switch from DBMS_SQL to native dynamic SQL
        cur := DBMS_SQL.TO_REFCURSOR(curid);
        FETCH cur INTO ...
    
        CLOSE cur;
    END;
    

    但我认为这有点矫枉过正。

    更新:

    您也可以动态组合 SQL 字符串,例如:

    sql_stmt := 'SELECT ';
    FOR i IN 1..31 LOOP
       sql_stmt := sql_stmt  || 'myField'||i||',';
    END LOOP;
    sql_stmt := REGEXP_REPLACE(sql_stmt, ',$');
    sql_stmt := sql_stmt  || ' FROM '||table_name;
    sql_stmt := sql_stmt  || ' WHERE the_date = :d';    
    OPEN cur FOR sql_stmt USING from_d; 
    

    【讨论】:

    • 我有很多不同的游标。相同的是模式(所有游标中相同的文件类型和相同数量的字段)。我不需要一个全局静态游标。
    • 您在我的代码中看到任何全局静态游标吗?
    • 我有很多不同的REP_HELPER 函数。 REP_HELPER1,....REP_HELPER55。所有REP_HELPER 函数内部都有不同的游标。你打电话给curid := REP_HELPER(...); 这不是我想要的,因为我只能定义一次光标......
    • 那么我建议只创建一个函数/过程和一个参数。否则需要动态 SQL:EXECUTE IMMEDIATE 'REP_HELPER' || 5 ||'(...)' INTO cur;
    • 我没有理解你的意思,一个函数多了一个参数......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-12
    • 1970-01-01
    • 2021-08-20
    • 2016-12-02
    • 2013-07-28
    • 2018-10-17
    相关资源
    最近更新 更多