今天在工作中遇到了一个比较头疼的问题:在进行一个几万条记录的表迁移时,该表中有几百条含有单引号的记录。
此表的处理过程是根据分析的深入而完善的。
1.最初,我们直接使用insert into…… select ……的句法直接将内容放入目标表中
归纳为:insert into dest_table select * from orig_table;
2.后来发现由于目标表中某些列设置的非空约束而使某些记录不能成功导入,因此引入了例外表,将不符合条件的内容放入"error_表名"格式的表中,使其不影响整个迁移
归纳为:insert into dest_table select * from orig_table log errors into error_dest_table ('insert'||to_char(sysdate,'yyyymmdd hh24:mi:ss')) reject limit unlimited;
3.源表和目标表的数据量都达到了八万多,重复进行fetch在性能上显然是不够优化的,因此想到在动态语句中使用bulk collect,将得到的查询结果一次性插入游标中。但发现有些列存在单引号,而使用例外表只能截获不符合约束的列,将多出单引号的列当做是缺少逗号来处理了,执行到这些地方就报错。--此想法暂告搁浅,但对于不含单引号的普通数据还是可行的
归纳为:execute immediate 'select * from orig_table ' bulk collect into cursor_name;
在循环中将游标中的数据逐条读取并insert到dest_table
'insert into dest_table values ('||''''||cursor_name(v_count).column_name)'||''''||
' log errors into error_dest_table(to_char(sysdate,'||''''||'yyyymmdd hh24:mi:ss'||''''||'))'||
' reject limit unlimited';
4.学习pl/sql时发现可以采用using子句,将数据存储之后,通过":参数名"进行调用,这种方式节省了变量的定义和引用量,最重要的是由于其不涉及到语句的拼凑,单引号的问题可以解决。
归纳为:execute immediate vc_select_statement bulk collect into v_column1,v_column2;
for i in 1..v_column1.count loop
insert_statement:='insert into dest_table values(:var_column1,:var_column2)'||
' log errors into error_dest_table(to_char(sysdate,'||''''||'yyyymmdd hh24:mi:ss'||''''||'))'||
' reject limit unlimited';
exectue immediate insert_statement using v_column1,v_column2;
end loop;
5.今天,看到bulk collect的limit子句,想着应该将这八万多条数据分批处理比较安全,做实验试试!
首先想到在方案四的基础上修改,但狡猾的execute immediate里似乎不能用limit子句。或许是execute immediate太方便了,需要点儿局限性吧。
这个问题至少应该是有方法处理的,首先想到的方法是在存储过程中将源表数据中单引号替换为其他字符(必须保证当前表中没有这个字符,如"~",或者较长的字符,但不要超过定义的列长),然后在方案三中方法的基础上对bulk collect使用limit子句进行限制。完成后将数据中的替换字符("~")替换为单引号。
此方法可以保证正确性,但时间消耗上比较大。
我们实验一下:
(1)环境构造
SQL> conn scott/tiger@admin
Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0
Connected as scott
SQL> create table tian as select table_name,column_name from dba_tab_columns;
Table created
SQL> select count(1) from tian;
COUNT(1)
----------
54203
SQL> select count(1) from tian where column_name like '%'||chr(39)||'%';
COUNT(1)
----------
0
//当前表中是没有含单引号的column_name的,我们将下划线替换为单引号
SQL> insert into tian select table_name,replace(column_name,'_',chr(39)) from tian;
54203 rows inserted
SQL> commit;
Commit complete
SQL> select count(1) from tian where column_name like '%'||chr(39)||'%';
COUNT(1)
----------
30208
SQL> select count(1) from tian ;
COUNT(1)
----------
108406
(2)存储过程编写
create or replace procedure mig_tian
is
type cursor_typ is ref cursor;
cursor_tian cursor_typ;--创建游标
type tian_typ is table of tian%rowtype;
tian_row tian_typ;--创建行类型
v_statement varchar2(300);--存储语句
v_count number(6);--计数用
begin
update tian set column_name=replace(column_name,chr(39),'~');--将单引号替换为~,这个语句挺简洁的 值得推荐!
commit;
open cursor_tian for 'select * from tian';
loop --循环插入数据,每次最多5000条
fetch cursor_tian bulk collect into tian_row limit 5000;
exit when tian_row.count=0;
v_count:=tian_row.count;
while (v_count>0)
loop
v_statement:='insert into dest_table values ('
||''''||tian_row(v_count).table_name||''''||','
||''''||tian_row(v_count).column_name||''''||')'||
' log errors into error_dest_table(to_char(sysdate,'||''''||'yyyymmdd hh24:mi:ss'||''''||'))'||
' reject limit unlimited';
execute immediate v_statement;
v_count:=v_count-1;
end loop;
commit;
end loop;
update tian set column_name=replace(column_name,'~',chr(39));--将单引号替换回来
commit;
exception when others then
dbms_output.put_line(sqlerrm);
end;
在后来的实验中,尝试将以上方法与方案四结合,也可以达到目的了。由于涉及生产环境信息,此部分略去。思想是一样的,通过limit指定每次插入游标的数据量,再通过循环将所有数据都执行一遍。利用using子句直接获取变量,不会产生单引号出错的麻烦。看来能oracle自己做的事,最好还是给它做吧。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/26451536/viewspace-746035/ ,如需转载,请注明出处,否则将追究法律责任。