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.
答案 0 :(得分: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设置可能会丢失精度)。