使用游标记录作为数组

Posted

技术标签:

【中文标题】使用游标记录作为数组【英文标题】:Using cursor record as arrays 【发布时间】:2017-08-13 13:47:09 【问题描述】:

我有一个 pl\sql 过程,需要使用 dbms_sql 包检查 curosr 循环中的记录。

游标查询是动态的,所以你不知道列。

所以每次我想使用 dbms_sql.define_columns 或其他函数时,我都会通过 all_tab_columns 上的循环来获取列名。

这是我的代码:

procedure p is

SOURCE_CURSOR      INTEGER; 
destination_cursor INTEGER; 
IGNORE             INTEGER; 
destination_cursor INTEGER; 
v_stmt clob := empty_clob();
V_COLS_LIST varchar2(4000);
V_COLS_LIST2 varchar2(4000);
V_UPDATE_DATE_COL_NAME varchar2(30) := 'UPDATE_DATE_COL';


begin

    -- going over all the records. each record is a table
    for CURR_TABLE in (select * from mng_tables)
    loop

        -- get the column list for the current table
        SELECT   LISTAGG(CLS.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_ID)
        INTO     V_COLS_LIST
        FROM     ALL_TAB_COLUMNS CLS
        WHERE    CLS.TABLE_NAME = CURR_TABLE.HISTORY_TABLE_NAME
        AND      CLS.OWNER = CURR_TABLE.HISTORY_TABLE_OWNER
        AND      CLS.COLUMN_NAME <> V_UPDATE_DATE_COL_NAME;

        -- prepare the select from current table
        v_stmt := 'select ' || V_COLS_LIST || ', SYSDATE' ||
                  ' from ' || CURR_TABLE.TABLE_OWNER || '.' || CURR_TABLE.TABLE_NAME;

        -- prepare the dynamic sql        

        -- get cursor id
        source_cursor := dbms_sql.open_cursor; 

        -- parse cursor with query
        DBMS_SQL.PARSE(SOURCE_CURSOR,V_STMT, DBMS_SQL.NATIVE); 


        -- going over all the columns of current table and define matching columns
        FOR rec in (SELECT * 
                      FROM ALL_TAB_COLUMNS 
                      WHERE CLS.TABLE_NAME = CURR_TABLE.HISTORY_TABLE_NAME
                      AND       CLS.OWNER = CURR_TABLE.HISTORY_TABLE_OWNER)
        loop

            DBMS_SQL.DEFINE_COLUMN(source_cursor, rec.column_id, rec.data_type); 

        end loop;

        -- execute the select query
        IGNORE := DBMS_SQL.EXECUTE(SOURCE_CURSOR); 

        -- define the destination cursor
        destination_cursor := DBMS_SQL.OPEN_CURSOR; 

        select replace(V_COLS_LIST, ',' , ':,')
        into   V_COLS_LIST2
        from dual;

        -- parse the 
        DBMS_SQL.PARSE(destination_cursor, 
              'insert /*+ parallel(8) */ into ' || CURR_TABLE.HISTORY_TABLE_OWNER || '.' ||  CURR_TABLE.HISTORY_TABLE_NAME ||
              '(' || V_COLS_LIST || ',' || V_UPDATE_DATE_COL_NAME || ')' ||
                ' values (:' || V_COLS_LIST2 || ',sysdate)', 
               DBMS_SQL.NATIVE);


        LOOP 

        -- if there is a row
        IF DBMS_SQL.FETCH_ROWS(source_cursor)>0 THEN 

         FOR rec in (SELECT * 
                      FROM ALL_TAB_COLUMNS 
                      WHERE CLS.TABLE_NAME = CURR_TABLE.HISTORY_TABLE_NAME
                      AND       CLS.OWNER = CURR_TABLE.HISTORY_TABLE_OWNER)
        loop

         -- get column values of the row 
         DBMS_SQL.COLUMN_VALUE(source_cursor, rec.column_id, ???); 

         DBMS_SQL.BIND_VARIABLE(destination_cursor, ':' || rec.column_name, ???); 


        end loop;

        ignore := DBMS_SQL.EXECUTE(destination_cursor); 

      ELSE 

  -- No more rows to copy: 
        EXIT; 
      END IF; 


        end loop;


    end loop;

end p;

但是当我想绑定变量时,我不能这样做,因为我不能动态地获取值。..

当我这样做时,程序结束时:

DBMS_SQL.COLUMN_VALUE(source_cursor, rec.column_id, ???); 

DBMS_SQL.BIND_VARIABLE(destination_cursor, ':' || rec.column_name, ???);

我只想替换 ???像“my_rec [rec.column_name]” 或“my_rec[rec.column_id]”,获取该列记录的值。

有什么想法吗?

谢谢。

【问题讨论】:

【参考方案1】:

你让这件事变得比需要的复杂得多,效率也更低。您可以生成一个 insert-as-select 类型的语句,该语句对每个表执行一次插入,而不是生成逐行插入并逐行选择和插入:

create or replace procedure p is
    v_stmt clob;
    v_cols_list varchar2(4000);
    v_update_date_col_name varchar2(30) := 'UPDATE_DATE_COL';
begin
    -- going over all the records. each record is a table
    for curr_table in (select * from mng_tables)
    loop
        -- get the column list for the current table
        select   '"' || listagg(cls.column_name, '","')
            within group (order by column_id) || '"'
        into     v_cols_list
        from     all_tab_columns cls
        where    cls.table_name = curr_table.history_table_name
        and      cls.owner = curr_table.history_table_owner
        and      cls.column_name <> v_update_date_col_name;

        -- generate an insert-select statement
        v_stmt := 'insert into "' || curr_table.history_table_owner || '"'
                || '."' || curr_table.history_table_name || '"'
            || ' (' || v_cols_list || ', ' || v_update_date_col_name || ')'
            || ' select ' || v_cols_list || ', sysdate'
            || ' from "' || curr_table.table_owner || '"'
                || '."' || curr_table.table_name || '"';

        -- just for debugging
        dbms_output.put_line(v_stmt);

        execute immediate v_stmt;
    end loop;
end p;
/

我已经在所有所有者、表和列名周围添加了双引号,以防万一您有任何带引号的标识符,但如果您确定永远不会,那么它们就没有必要了。

不过,要回答您的实际问题,简单的蛮力方法是声明一个字符串变量:

v_value varchar2(4000);

然后在column_value和bind_variable`调用中使用比:

DBMS_SQL.COLUMN_VALUE(source_cursor, rec.column_id, v_value); 
DBMS_SQL.BIND_VARIABLE(destination_cursor, rec.column_name, v_value); 

您发布的内容存在许多问题,从 CLS.TABLE_NAME 之类的引用开始,当您在两个循环中没有 CLS 别名时(也不排除您的 V_UPDATE_DATE_COL_NAME柱子);您的 DEFINE_COLUMN 调用未指定数据长度,因此它无法正常用于字符串列;您的 replace() 将冒号放在逗号之前而不是之后;你声明了destination_cursor 两次。

但是,如果我了解您的架构,这可行:

create or replace procedure p is

SOURCE_CURSOR      INTEGER; 
destination_cursor INTEGER; 
IGNORE             INTEGER; 
v_stmt clob := empty_clob();
V_COLS_LIST varchar2(4000);
V_COLS_LIST2 varchar2(4000);
V_UPDATE_DATE_COL_NAME varchar2(30) := 'UPDATE_DATE_COL';
v_value varchar2(4000);

begin

    -- going over all the records. each record is a table
    for CURR_TABLE in (select * from mng_tables)
    loop

        -- get the column list for the current table
        SELECT   LISTAGG(CLS.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_ID)
        INTO     V_COLS_LIST
        FROM     ALL_TAB_COLUMNS CLS
        WHERE    CLS.TABLE_NAME = CURR_TABLE.HISTORY_TABLE_NAME
        AND      CLS.OWNER = CURR_TABLE.HISTORY_TABLE_OWNER
        AND      CLS.COLUMN_NAME <> V_UPDATE_DATE_COL_NAME;

        -- prepare the select from current table
        v_stmt := 'select ' || V_COLS_LIST || ', SYSDATE' ||
                  ' from ' || CURR_TABLE.TABLE_OWNER || '.' || CURR_TABLE.TABLE_NAME;

        -- prepare the dynamic sql        

        -- get cursor id
        source_cursor := dbms_sql.open_cursor; 

        -- parse cursor with query
        DBMS_SQL.PARSE(SOURCE_CURSOR,V_STMT, DBMS_SQL.NATIVE); 


        -- going over all the columns of current table and define matching columns
        FOR rec in (SELECT * 
                      FROM ALL_TAB_COLUMNS CLS
                      WHERE CLS.TABLE_NAME = CURR_TABLE.HISTORY_TABLE_NAME
                      AND       CLS.OWNER = CURR_TABLE.HISTORY_TABLE_OWNER
                      AND       CLS.COLUMN_NAME <> V_UPDATE_DATE_COL_NAME)
        loop

            DBMS_SQL.DEFINE_COLUMN(source_cursor, rec.column_id, rec.data_type, rec.data_length); 

        end loop;

        -- execute the select query
        IGNORE := DBMS_SQL.EXECUTE(SOURCE_CURSOR); 

        -- define the destination cursor
        destination_cursor := DBMS_SQL.OPEN_CURSOR;

        select replace(V_COLS_LIST, ',' , ',:')
        into   V_COLS_LIST2
        from dual;

        -- parse the 
        DBMS_SQL.PARSE(destination_cursor, 
              'insert /*+ parallel(8) */ into ' || CURR_TABLE.HISTORY_TABLE_OWNER || '.' ||  CURR_TABLE.HISTORY_TABLE_NAME ||
              '(' || V_COLS_LIST || ',' || V_UPDATE_DATE_COL_NAME || ')' ||
                ' values (:' || V_COLS_LIST2 || ',sysdate)', 
               DBMS_SQL.NATIVE);


        LOOP 

        -- if there is a row
        IF DBMS_SQL.FETCH_ROWS(source_cursor)>0 THEN 

         FOR rec in (SELECT * 
                      FROM ALL_TAB_COLUMNS CLS
                      WHERE CLS.TABLE_NAME = CURR_TABLE.HISTORY_TABLE_NAME
                      AND       CLS.OWNER = CURR_TABLE.HISTORY_TABLE_OWNER
                      AND       CLS.COLUMN_NAME <> V_UPDATE_DATE_COL_NAME)
        loop

         -- get column values of the row 
         DBMS_SQL.COLUMN_VALUE(source_cursor, rec.column_id, v_value); 

         DBMS_SQL.BIND_VARIABLE(destination_cursor, rec.column_name, v_value); 


        end loop;

        ignore := DBMS_SQL.EXECUTE(destination_cursor); 

        dbms_sql.close_cursor(destination_cursor);
      ELSE 

  -- No more rows to copy: 
        EXIT; 
      END IF; 


        end loop;


    end loop;

end p;
/

最好有一个每种可能数据类型的变量,并使用 case 语句调用 column_value 和 bind_variable`,并为每列使用正确类型的变量,这样您就不必依赖于和的隐式转换来自字符串(尤其是日期问题 - 根据会话 NLS 设置可能会丢失精度)。

【讨论】:

我有理由不使用inser-select,即使它实际上是微不足道的方式。我修复了已发布的问题,当您不输入我的原始代码时会发生这种情况。您的代码帮助了我!非常感谢!

以上是关于使用游标记录作为数组的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server基础操作(此随笔仅作为本人学习进度记录九!--游标)

带有参数化游标的嵌套 for 循环正在插入重复记录

Oracle_PL/SQL 游标

Oracle SQL

基于游标的记录与强引用游标

PL/SQL 编程游标存储过程函数