0

I have a pl\sql procedure that need to go over records in a curosr loop using dbms_sql package.

the cursor query is dynamic, so you don't know the columns.

so each time I want to use dbms_sql.define_columns or others functions I do it by a loop on all_tab_columns to get the columns names.

This is my code:

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;

but when I want to bind the variables, I just can't do that becuase I can't have the values dynamically..

In the end of procedure when I'm doing that:

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

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

I just want to replace the ??? with something like "my_rec[rec.column_name]" or "my_rec[rec.column_id]" and get the value of the record in this column.

Any idea?

Thanks.

1 Answer 1

1

You are making this much more complicated - and less efficient - than it needs to be. Rather than generating a line-by-line insert and selecting and inserting each row one by one, you can generate an insert-as-select type statement that does a single insert per table:

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

I've added double-quotes around all the owner, table and column names just in case you have any quoted identifiers, but if you're sure you never will then they aren't really necessary.

To answer your actual question though, the simple brute-force way is to declare a single string variable:

v_value varchar2(4000);

and then use than in the column_value and bind_variable` calls:

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

There are a number of issues with what you've posted, starting with references like CLS.TABLE_NAME when you haven't got a CLS alias in two of the loops (which also don't exclude your V_UPDATE_DATE_COL_NAME column); your DEFINE_COLUMN call isn't specifying the data length so it won't work properly for string columns; your replace() is putting the colon before the commas instead of after it; and you're declaring destination_cursor twice.

But this works, if I've understood your schema:

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

It would be better to have a variable of each possible data type and use a case statement to call column_value and bind_variable` with the correctly-typed variable for each column, so you aren't relying on implicit conversion to and from strings (particularly a problem with dates - which could lose precision depending on the session NLS settings).

Sign up to request clarification or add additional context in comments.

1 Comment

I have my reason for do not use inser-select, even it's actually the trivial way. I fixed the issued, that's what happens when you don't put my original code.. Your code helped me ! Thanks a lot!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.