具有参数化模式名称并用于目标表中的 BULK INSERT 的动态游标
Posted
技术标签:
【中文标题】具有参数化模式名称并用于目标表中的 BULK INSERT 的动态游标【英文标题】:Dynamic Cursor with parameterised schema name and using for BULK INSERT in target table 【发布时间】:2019-09-11 11:31:36 【问题描述】:我在不同的 22 个模式中有 source_table,需要为批量收集创建过程并插入到 oracle 存储过程中的同一目标表中。
我正在尝试但没有插入记录,出现错误 ORA-00911: invalid character but there are all column from select cursor and traget_table are the order.
CREATE OR REPLACE PROCEDURE proc_bulk_circle(p_limit IN PLS_INTEGER DEFAULT 10000,
p_activity_date IN DATE,
p_circle IN VARCHAR2) AS
CURSOR act_cur IS
SELECT activity_date,
circle
FROM circle_load_control
WHERE activity_date = p_activity_date
AND circle = circle;
TYPE type_i6 IS TABLE OF act_cur%ROWTYPE INDEX BY BINARY_INTEGER;
i_tab6 type_i6;
v_count NUMBER := 0;
lv_circle VARCHAR2(2);
lv_schema VARCHAR2(20);
TYPE rc IS REF CURSOR;
con_sap_cur rc;
TYPE con_sap_resp IS TABLE OF target_table%ROWTYPE INDEX BY BINARY_INTEGER;
i_tab1 con_sap_resp;
lv_sql_stmt VARCHAR2(32767);
BEGIN
IF p_circle = 'MUM'
THEN
lv_circle := 'MU';
lv_schema := 'MUMBAI';
ELSIF p_circle = 'MAH'
THEN
lv_circle := 'MH';
lv_schema := 'MHRSTR';
ELSE
lv_circle := NULL;
END IF;
FOR myindex IN act_cur
LOOP
i_tab6(v_count) := myindex;
v_count := v_count + 1;
END LOOP;
FOR myindex IN i_tab6.first .. i_tab6.last
LOOP
IF i_tab6(myindex).activity_date = p_activity_date
AND i_tab6(myindex).circle = p_circle
THEN
BEGIN
lv_sql_stmt := 'SELECT acc_id code,
cust_id c_id,
addr_1 address2,
addr_2 address3,
addr_3 address4,
(SELECT SUM(abc) FROM ' || lv_schema || '.details WHERE <some condition with t1> GROUP BY <columns>) main_charges,
(SELECT SUM(extra_charge) FROM ' || lv_schema || '.details WHERE <some condition with t1> GROUP BY <columns>) extra_charges
FROM ' || lv_schema || '.main_source_details t1
WHERE t1.activity_date = ''' || p_activity_date || ''';';
OPEN con_sap_cur FOR lv_sql_stmt;
LOOP
FETCH con_sap_cur BULK COLLECT
INTO i_tab1 LIMIT p_limit;
FORALL i IN 1 .. i_tab1.count
INSERT INTO target_table (column list....)
VALUES(I_TAB1(i).col1,......;
EXIT WHEN con_sap_cur%NOTFOUND;
END LOOP;
COMMIT;
CLOSE con_sap_cur;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('ERR target_table: ' || SQLCODE || '-' || SQLERRM);
END;
ELSE
dbms_output.put_line(p_activity_date || ' DATE IS NOT MATCH');
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
END proc_bulk_circle;
/
【问题讨论】:
一些事情:act_cur 是否必要?您似乎没有使用它的结果,除非它是验证检查?您是否期望从中返回多行,以便将相同的行多次插入到 main_source_detail 表中,还是应该只将数据插入相关表中一次?我问,因为从光标到您要插入的数据似乎没有链接。 如果您发布一个实际重现问题的代码的简化版本,帮助您会容易得多。伪代码留下了很多混淆和误报的机会。您甚至可以使用 livesql.oracle.com 创建脚本,然后向我们提供链接(以及粘贴到您的问题中)。 感谢 Boneist 和 Steven Feuerstein 的建议。 顺便说一句,SQLCODE || '-' || SQLERRM
在我看来总是一个错误。你看过输出了吗?
【参考方案1】:
我相信这归结为你有一个 ;在你的 sql 定义中(见下一行)
WHERE t1.activity_date = ''' || p_activity_date || ''';';
当您为动态使用定义 SQL(并且以这种方式打开游标是动态的)时,您不包括 ;
为了说明这一点,我做了一个较短的示例。以下将与您的错误方式相同。
declare
v_sql varchar2(100) default 'select ''X'' from dual;';
TYPE rc IS REF CURSOR;
v_cur rc;
type l_tab_type is table of varchar2(1);
l_tab l_tab_type;
begin
open v_cur for v_sql;
loop
fetch v_cur bulk collect into l_tab;
exit;
end loop;
CLOSE v_cur;
end;
/
但只需删除 ;从线
v_sql varchar2(100) default 'select ''X'' from dual;';
结束一切正常,下面的固定示例。
declare
v_sql varchar2(100) default 'select ''X'' from dual';
TYPE rc IS REF CURSOR;
v_cur rc;
type l_tab_type is table of varchar2(1);
l_tab l_tab_type;
begin
open v_cur for v_sql;
loop
fetch v_cur bulk collect into l_tab;
exit;
end loop;
CLOSE v_cur;
end;
/
【讨论】:
太棒了,你的建议奏效了......谢谢@Shaun Peterson。 没问题@Radhesh,因为它有效,请接受答案,以便其他人知道它已经有解决方案。谢谢【参考方案2】:如果您的目的是插入一些行,那么您在这里做了很多工作。
相反,您可以一次性完成插入和选择,例如:
CREATE OR REPLACE PROCEDURE proc_bulk_circle(p_activity_date IN DATE,
p_circle IN VARCHAR2) AS
lv_circle VARCHAR2(2);
lv_schema VARCHAR2(20);
v_query CLOB;
e_table_does_not_exist EXCEPTION;
PRAGMA EXCEPTION_INIT(e_table_does_not_exist, -00942);
BEGIN
IF p_circle = 'MUM'
THEN
lv_circle := 'MU';
lv_schema := 'MUMBAI';
ELSIF p_circle = 'MAH'
THEN
lv_circle := 'MH';
lv_schema := 'MHRSTR';
END IF;
IF lv_schema IS NOT NULL
THEN
-- asserting the schema name to avoid sql injection
-- also using a bind variable for the activity_daate predicates
v_query := 'INSERT INTO target_table (<column list>)' || CHR(10) ||
' WITH main_dets AS (SELECT acc_id,' || CHR(10) ||
' cust_id,' || CHR(10) ||
' addr_1,' || CHR(10) ||
' addr_2,' || CHR(10) ||
' addr_3,' || CHR(10) ||
' (SELECT SUM(abc) FROM ' || dbms_assert.simple_sql_name(lv_schema) || '.details WHERE <some condition with t1>) main_charges,' || CHR(10) || -- no need for the group by
' (SELECT SUM(extra_charge) FROM ' || dbms_assert.simple_sql_name(lv_schema) || '.details WHERE <some condition with t1>) extra_charges' || CHR(10) || -- no need for the group by
' FROM ' || dbms_assert.simple_sql_name(lv_schema) || '.main_source_details t1' || CHR(10) ||
' WHERE activity_date = :p_activity_date)' || CHR(10) ||
' circles AS (SELECT activity_date,' || CHR(10) ||
' circle' || CHR(10) ||
' FROM circle_load_control' || CHR(10) ||
' WHERE activity_date = :p_activity_date' || CHR(10) ||
' AND circle = circle)' || CHR(10) || -- did you really mean circle = circle here? This is equivalent to 1=1 (unless circle is null) and is therefore pretty irrelevant! If you want to exclude rows with null values, use "circle is not null" instead
' SELECT md.acc_id,' || CHR(10) ||
' md.cust_id,' || CHR(10) ||
' md.addr_1,' || CHR(10) ||
' md.addr_2,' || CHR(10) ||
' md.addr_3,' || CHR(10) ||
' md.main_charges,' || CHR(10) ||
' md.extra_charges' || CHR(10) ||
' FROM main_dets md' || CHR(10) ||
' CROSS JOIN circles c';
EXECUTE v_query USING p_activity_date, p_activity_date;
COMMIT;
ELSE
raise_application_error(-20001, 'Invalid circle specified: "' || p_circle || '"');
END IF;
END proc_bulk_circle;
/
(注:未经测试。)
我假设 circle_load_control 中的 activity_date 和 circle 不是唯一的;如果是这样,您可以避免交叉连接,只需在执行 IF p_circle = ...
检查之前使用隐式游标来获取行。
【讨论】:
其实circle = circle
和1=1
不一样是圈子可以是null
以上是关于具有参数化模式名称并用于目标表中的 BULK INSERT 的动态游标的主要内容,如果未能解决你的问题,请参考以下文章
我可以将 Excel 工作表中的行加载到 BULK 的数据库表中吗