0

I need to do a FOR EACH loop in a procedure, but I need to pass the table name dynamically.

This is the declaration

CREATE OR REPLACE PROCEDURE MIGRATE_PRIMITIVES_PROPS
(
    FromTable IN VARCHAR2, 
    ToTable IN VARCHAR2
)

When I try and do this FOR EachRow IN (SELECT * FROM FromTable) It says the table isn't valid

The table coming into the procedure is dynamic, columns are added and deleted all the time so I can't spell out the columns and use a cursor to populate them.

DanTheMan1966
  • 45
  • 1
  • 11
  • I have. A code snippet would sure help. – DanTheMan1966 May 22 '15 at 13:42
  • How are you going to refer to the column within the loop (`EachRow.col1` etc) if you [don't know their names at compile time](http://stackoverflow.com/q/6478419/266304) either? Do you know some static ones that you are interested in; or is the structure completely dynamic? What are you doing with the data - hopefully not just inserting it into `ToTable`? – Alex Poole May 22 '15 at 13:54
  • `FromTable is not a static table name. As already suggested, look up dynamic sql. – Lalit Kumar B May 22 '15 at 13:56
  • I do know the names of the columns that make up the primary key. The ToTable is a key-value pair table. I know that isn't good practice, but the powers that be have all insisted on it. So I'm grabbing all of the column names that aren't in the PK of the FromTable and putting them in the ToTable. – DanTheMan1966 May 22 '15 at 14:00
  • There is a second FOR loop where I'm doing this FOR EachColumn IN (SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = ''||FromTable||'' AND COLUMN_NAME != 'COL_NAME' AND COLUMN_NAME != 'LIB_NAME' AND COLUMN_NAME != 'PARTNAME' AND COLUMN_NAME != 'PRIMITIVE' AND COLUMN_NAME != 'PART_ROW') which works, since the table in that query can be dynamically inserted. I just need help getting the first FOR loop working: FOR EachRow IN (SELECT * FROM FromTable) – DanTheMan1966 May 22 '15 at 14:02
  • So you do need to find the column names dynamically too? It sounds like you're doing a dynamic unpivot. – Alex Poole May 22 '15 at 14:02

1 Answers1

1

You have to use dynamic SQL to query a table whose name you don't know at compile time. You can do that with a dynamic cursor:

as
    l_cursor sys_refcursor;
begin
    open l_cursor for 'select * from ' || fromtable;
    loop
      fetch l_cursor into ... 

... but then it breaks down because you can't define a record type to fetch into based on a weak ref cursor; and you don't know the column names or types you're actually interested in - you're using select * and have specific names to exclude, not include. You mentioned an inner loop that works and gets the column names, but there is no way to refer to a field in that cursor variable dynamically either.

So you have to work a bit harder and use the dbms_sql package instead of native dynamic SQL.

Here's a basic version:

create or replace procedure migrate_primitives_props
(
    fromtable in varchar2, 
    totable in varchar2
)
as
    l_cursor pls_integer;
    l_desc_tab dbms_sql.desc_tab;
    l_columns pls_integer;
    l_value varchar2(4000);
    l_status pls_integer;
begin
    l_cursor := dbms_sql.open_cursor;

    -- parse the query using the parameter table name
    dbms_sql.parse(l_cursor, 'select * from ' || fromtable, dbms_sql.native);
    dbms_sql.describe_columns(l_cursor, l_columns, l_desc_tab);

    -- define all of the columns
    for i in 1..l_columns loop
        dbms_sql.define_column(l_cursor, i, l_value, 4000);
    end loop;

    -- execute the cursor query
    l_status := dbms_sql.execute(l_cursor);

    -- loop over the rows in the result set
    while (dbms_sql.fetch_rows(l_cursor) > 0) loop
        -- loop over the columns in each row
        for i in 1..l_columns loop
            -- skip the columns you aren't interested in
            if l_desc_tab(i).col_name in ('COL_NAME', 'LIB_NAME', 'PARTNAME',
                'PRIMITIVE', 'PART_ROW')
            then
                continue;
            end if;

            -- get the column value for this row
            dbms_sql.column_value(l_cursor, i, l_value);
            -- insert the key-value pair for this row
            execute immediate 'insert into ' || totable
                || '(key, value) values (:key, :value)'
                using l_desc_tab(i).col_name, l_value;
        end loop;
    end loop;
end;
/

I've assumed you know the column names in your ToTable but still used a dynamic insert statement since that table name is unknown. (Which seems strange, but...)

Creating and populating sample tables, and then calling the procedure with their names:

create table source_table (col_name varchar2(30), lib_name varchar2(30),
  partname varchar2(30), primitive number, part_row number,
  col1 varchar2(10), col2 number, col3 date);
create table target_table (key varchar2(30), value varchar2(30));

insert into source_table (col_name, lib_name, partname, primitive, part_row,
  col1, col2, col3)
values ('A', 'B', 'C', 0, 1, 'Test', 42, sysdate);

exec migrate_primitives_props('source_table', 'target_table');

End up with the target table containing:

select * from target_table;

KEY                            VALUE                        
------------------------------ ------------------------------
COL1                           Test                          
COL2                           42                            
COL3                           2015-05-22 15:29:31           

It's basic because it isn't sanitising the inputs (look up the dbms_assert package), and isn't doing any special handling for different data types. In my example my source table had a date column; the target table gets a string representation of that date value based on the calling session's NLS_DATE_FORMAT setting, which isn't ideal. There's a simple but slightly hacky way to get a consistent date format, and a better but more complicated way; but you may not have date values so this might be good enough as it is.

Community
  • 1
  • 1
Alex Poole
  • 183,384
  • 11
  • 179
  • 318
  • Thanks. That's really close. The primary key of the Key-Value table is also the same as the PRIMITIVES_PROPS table so still a little work to do, but closer. – DanTheMan1966 May 22 '15 at 17:51
  • @DanTheMan1966 - if you don't know the column position and can't get it by name, you could always do two loops over the columns; the first that only looks for the named column to get the key into a variable, then the second as above, that inserts the row including the key set in the first loop. (I think that makes sense...) – Alex Poole May 22 '15 at 18:18